clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/tinyagent
tinyrelay / internal / daemon / routes_acceptance_test.go
routes_acceptance_test.go 5.0K
1package daemon23import (4 "bytes"5 "context"6 "encoding/json"7 "net/http"8 "net/http/httptest"9 "testing"10)1112func mountedRequest(t *testing.T, app *App, method, path string, body []byte, accept string, signed bool) *httptest.ResponseRecorder {13 req := httptest.NewRequest(method, "http://relay.test/r/main"+path, bytes.NewReader(body))14 if accept != "" {15 req.Header.Set("Accept", accept)16 }17 if signed {18 signRequest(t, req, string(body))19 }20 res := httptest.NewRecorder()21 app.ServeHTTP(res, req)22 return res23}2425func TestMountedRouteNIP11AndBridgeAcceptance(t *testing.T) {26 app, _ := testTenant(t)27 info := mountedRequest(t, app, http.MethodGet, "/", nil, "application/nostr+json", false)28 if info.Code != http.StatusOK || info.Header().Get("Content-Type") != "application/nostr+json" {29 t.Fatalf("NIP-11 status/content-type=%d/%q", info.Code, info.Header().Get("Content-Type"))30 }31 var document map[string]any32 if err := json.Unmarshal(info.Body.Bytes(), &document); err != nil {33 t.Fatal(err)34 }35 if _, ok := document["fuel"]; ok {36 t.Fatal("NIP-11 exposes removed fuel capability")37 }3839 query := []byte(`[{"kinds":[1]}]`)40 unauth := mountedRequest(t, app, http.MethodPost, "/query", query, "", false)41 if unauth.Code != http.StatusUnauthorized {42 t.Fatalf("unsigned bridge query=%d", unauth.Code)43 }44 auth := mountedRequest(t, app, http.MethodPost, "/query", query, "", true)45 if auth.Code != http.StatusOK {46 t.Fatalf("signed bridge query=%d body=%s", auth.Code, auth.Body.String())47 }48 bad := mountedRequest(t, app, http.MethodPost, "/query", []byte(`[{`), "", true)49 if bad.Code != http.StatusBadRequest {50 t.Fatalf("malformed bridge query=%d", bad.Code)51 }52}5354func TestMountedRouteNIP86CORSAndPublicPages(t *testing.T) {55 app, _ := testTenant(t)56 body := []byte(`{"method":"listviews","params":[]}`)57 unauthReq := httptest.NewRequest(http.MethodPost, "http://relay.test/r/main/manage", bytes.NewReader(body))58 unauthReq.Header.Set("Content-Type", "application/nostr+json+rpc")59 unauthRes := httptest.NewRecorder()60 app.ServeHTTP(unauthRes, unauthReq)61 unauth := unauthRes62 if unauth.Code != http.StatusUnauthorized {63 t.Fatalf("unsigned NIP-86=%d", unauth.Code)64 }65 authReq := httptest.NewRequest(http.MethodPost, "http://relay.test/r/main/manage", bytes.NewReader(body))66 authReq.Header.Set("Content-Type", "application/nostr+json+rpc")67 signRequest(t, authReq, string(body))68 authRes := httptest.NewRecorder()69 app.ServeHTTP(authRes, authReq)70 auth := authRes71 if auth.Code != http.StatusOK {72 t.Fatalf("signed NIP-86=%d body=%s", auth.Code, auth.Body.String())73 }74 for _, path := range []string{"/", "/people", "/card.json", "/connect.json", "/terms", "/invite/missing"} {75 res := mountedRequest(t, app, http.MethodGet, path, nil, "", path == "/people" || path == "/connect.json")76 if res.Code != http.StatusOK && !(path == "/invite/missing" && res.Code == http.StatusNotFound) {77 t.Fatalf("public route %s=%d body=%s", path, res.Code, res.Body.String())78 }79 }80 preflight := mountedRequest(t, app, http.MethodOptions, "/query", nil, "", false)81 if preflight.Code != http.StatusNoContent || preflight.Header().Get("Access-Control-Allow-Origin") != "*" {82 t.Fatalf("CORS preflight=%d origin=%q", preflight.Code, preflight.Header().Get("Access-Control-Allow-Origin"))83 }84}8586func TestMountedRouteNIP05WebAddressAndDataAuth(t *testing.T) {87 app, tenant := testTenant(t)88 p := tenant.Policy()89 p.Features.Names = true90 if err := tenant.applyPolicy(context.Background(), p); err != nil {91 t.Fatal(err)92 }93 unknown := mountedRequest(t, app, http.MethodGet, "/.well-known/nostr.json?name=missing", nil, "", false)94 if unknown.Code != http.StatusOK {95 t.Fatalf("unknown NIP-05=%d", unknown.Code)96 }97 var emptyNIP05 struct {98 Names map[string]string `json:"names"`99 }100 if err := json.Unmarshal(unknown.Body.Bytes(), &emptyNIP05); err != nil || len(emptyNIP05.Names) != 0 {101 t.Fatalf("unknown NIP-05 document=%s", unknown.Body.String())102 }103 web := mountedRequest(t, app, http.MethodGet, "/.well-known/nostr.json?path=/npub1missing", nil, "", false)104 if web.Code != http.StatusUnauthorized {105 t.Fatalf("unsigned NIP-AD=%d", web.Code)106 }107 importRes := mountedRequest(t, app, http.MethodPut, "/import", []byte("{}\n"), "", false)108 if importRes.Code != http.StatusForbidden {109 t.Fatalf("unsigned import=%d", importRes.Code)110 }111 backup := mountedRequest(t, app, http.MethodGet, "/backups/missing.json", nil, "", false)112 if backup.Code != http.StatusUnauthorized && backup.Code != http.StatusForbidden && backup.Code != http.StatusNotFound {113 t.Fatalf("backup visibility=%d", backup.Code)114 }115 if tenant.Policy().Features.Files {116 // The dedicated NIP-96 test covers upload/delete; this assertion keeps117 // the mounted route's disabled-file behavior explicit for this matrix.118 p := tenant.Policy()119 p.Features.Files = false120 if err := tenant.applyPolicy(context.Background(), p); err != nil {121 t.Fatal(err)122 }123 blob := mountedRequest(t, app, http.MethodGet, "/nip96/missing", nil, "", false)124 if blob.Code != http.StatusNotFound {125 t.Fatalf("disabled blob route=%d", blob.Code)126 }127 }128}