clone

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

feat/tinyagent
tinyrelay / internal / daemon / ux_contract_test.go
ux_contract_test.go 4.0K
1package daemon23import (4 "bytes"5 "context"6 "encoding/json"7 "net/http"8 "net/http/httptest"9 "strings"10 "testing"11)1213// These are the exact argument shapes emitted by the guided Sync forms. The14// test is deliberately service-level so it catches UI/backend drift without15// depending on template markup.16func TestGuidedSyncArgumentContracts(t *testing.T) {17 ctx := context.Background()18 _, tenant := testTenant(t)19 filter := json.RawMessage(`{"kinds":[1],"limit":10}`)20 if _, err := tenant.replication.ExecuteRaw(ctx, "pullfrom", []json.RawMessage{json.RawMessage(`"https://relay.example"`), filter}); err != nil {21 t.Fatalf("pullfrom guided args: %v", err)22 }23 if _, err := tenant.replication.ExecuteRaw(ctx, "backfill", []json.RawMessage{json.RawMessage(`{"relays":["https://relay.example"]}`)}); err != nil {24 t.Fatalf("backfill guided args: %v", err)25 }26 if _, err := tenant.community.Execute(ctx, tenant.Policy().Owner, "createinvite", []json.RawMessage{json.RawMessage(`259200`), json.RawMessage(`1`), json.RawMessage(`note`)}); err != nil {27 t.Fatalf("createinvite guided args: %v", err)28 }29 if _, err := tenant.community.Execute(ctx, tenant.Policy().Owner, "setmember", []json.RawMessage{json.RawMessage(`"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"`), json.RawMessage(`{"name":"reader","note":"hello"}`)}); err != nil {30 t.Fatalf("setmember guided args: %v", err)31 }32}3334func TestSignedManagementJourneys(t *testing.T) {35 ctx := context.Background()36 app, tenant := testTenant(t)37 owner := tenant.Policy().Owner38 call := func(method string, params ...any) map[string]any {39 t.Helper()40 rawParams := make([]json.RawMessage, len(params))41 for i, value := range params {42 raw, err := json.Marshal(value)43 if err != nil {44 t.Fatal(err)45 }46 rawParams[i] = raw47 }48 body, err := json.Marshal(map[string]any{"method": method, "params": rawParams})49 if err != nil {50 t.Fatal(err)51 }52 req := httptest.NewRequest(http.MethodPost, "http://relay.test/", bytes.NewReader(body))53 req.Header.Set("Content-Type", "application/nostr+json+rpc")54 signRequest(t, req, string(body))55 res := httptest.NewRecorder()56 tenant.ServeHTTP(res, req)57 if res.Code != http.StatusOK {58 t.Fatalf("%s status=%d body=%s", method, res.Code, res.Body.String())59 }60 var response map[string]any61 if err := json.Unmarshal(res.Body.Bytes(), &response); err != nil {62 t.Fatal(err)63 }64 return response65 }66 invite := call("createinvite", 3600, 1, "guided")67 if invite["result"] == nil {68 t.Fatal("createinvite returned no result")69 }70 call("listinvites")71 member := strings.Repeat("2", 64)72 call("setmember", member, map[string]any{"name": "reader", "note": "guided"})73 call("banpubkey", member, "test")74 call("allowpubkey", member, "restored")75 call("blockip", "192.0.2.1", "test")76 call("unblockip", "192.0.2.1")77 call("setpolicy", map[string]any{"description": "guided"})78 call("changerelayname", "guided relay")79 call("changerelaydescription", "guided description")80 call("publishview", "articles")81 call("exportconfig")82 call("forkrelay", map[string]any{"name": "guided-fork", "owner": owner, "people": true})83 call("removemember", member)8485 // Deletion has its own tenant so it cannot invalidate the remaining journey.86 meta, err := app.Create(ctx, CreateOptions{Name: "guided-delete", Owner: owner, Template: "default"})87 if err != nil {88 t.Fatal(err)89 }90 deleteTenant, err := app.tenant(ctx, meta, "http://relay.test/r/guided-delete")91 if err != nil {92 t.Fatal(err)93 }94 deleteBody, _ := json.Marshal(map[string]any{"method": "deleterelay", "params": []json.RawMessage{json.RawMessage(`"guided-delete"`)}})95 deleteReq := httptest.NewRequest(http.MethodPost, "http://relay.test/", bytes.NewReader(deleteBody))96 deleteReq.Header.Set("Content-Type", "application/nostr+json+rpc")97 signRequest(t, deleteReq, string(deleteBody))98 deleteRes := httptest.NewRecorder()99 deleteTenant.ServeHTTP(deleteRes, deleteReq)100 if deleteRes.Code != http.StatusOK {101 t.Fatalf("deleterelay status=%d body=%s", deleteRes.Code, deleteRes.Body.String())102 }103}