Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package xrpc 2 3import ( 4 "encoding/json" 5 "net/http" 6 "net/http/httptest" 7 "net/url" 8 "testing" 9 10 "tangled.org/core/api/tangled" 11) 12 13func listRequest(t *testing.T, params url.Values) *http.Request { 14 t.Helper() 15 return httptest.NewRequest(http.MethodGet, "/xrpc/test?"+params.Encode(), nil) 16} 17 18func decodeMembers(t *testing.T, rec *httptest.ResponseRecorder) tangled.KnotListMembers_Output { 19 t.Helper() 20 var out tangled.KnotListMembers_Output 21 if err := json.NewDecoder(rec.Body).Decode(&out); err != nil { 22 t.Fatalf("decode members: %v", err) 23 } 24 return out 25} 26 27func seedMembers(t *testing.T, x *Xrpc, subjects ...string) { 28 t.Helper() 29 for _, s := range subjects { 30 rec := httptest.NewRecorder() 31 x.AddMember(rec, aclRequest(t, aclOwner, tangled.KnotAddMember_Input{Subject: s})) 32 if rec.Code != http.StatusOK { 33 t.Fatalf("seed member %s: status %d, body=%s", s, rec.Code, rec.Body.String()) 34 } 35 } 36} 37 38func TestListMembers_ReturnsAddedSubjects(t *testing.T) { 39 x, _ := newACLXrpc(t) 40 seedMembers(t, x, aclSubject, "did:plc:scallop") 41 42 rec := httptest.NewRecorder() 43 x.ListMembers(rec, listRequest(t, url.Values{"subject": {"knot.example"}})) 44 if rec.Code != http.StatusOK { 45 t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) 46 } 47 48 out := decodeMembers(t, rec) 49 if len(out.Items) != 2 { 50 t.Fatalf("items = %d, want 2", len(out.Items)) 51 } 52 if out.Cursor != nil { 53 t.Errorf("cursor = %q, want nil for a complete page", *out.Cursor) 54 } 55 56 for _, it := range out.Items { 57 if it.AddedBy != aclOwner { 58 t.Errorf("subject %s addedBy = %q, want %s", it.Subject, it.AddedBy, aclOwner) 59 } 60 if it.CreatedAt == "" { 61 t.Errorf("subject %s has empty createdAt", it.Subject) 62 } 63 if it.Uri != nil || it.Cid != nil { 64 t.Errorf("subject %s carries record uri/cid; a knot must omit them", it.Subject) 65 } 66 } 67} 68 69func TestListMembers_Empty(t *testing.T) { 70 x, _ := newACLXrpc(t) 71 rec := httptest.NewRecorder() 72 x.ListMembers(rec, listRequest(t, url.Values{"subject": {"knot.example"}})) 73 if rec.Code != http.StatusOK { 74 t.Fatalf("status = %d, want 200", rec.Code) 75 } 76 out := decodeMembers(t, rec) 77 if len(out.Items) != 0 { 78 t.Errorf("items = %d, want 0", len(out.Items)) 79 } 80} 81 82func TestListMembers_RejectsMalformedParams(t *testing.T) { 83 x, _ := newACLXrpc(t) 84 for _, q := range []url.Values{ 85 {"limit": {"abc"}}, 86 {"cursor": {"notanint"}}, 87 {"order": {"ascending"}}, 88 } { 89 rec := httptest.NewRecorder() 90 x.ListMembers(rec, listRequest(t, q)) 91 if rec.Code != http.StatusBadRequest { 92 t.Errorf("params %v: status = %d, want 400", q, rec.Code) 93 } 94 } 95} 96 97func TestListMembers_ClampsOutOfRangeLimit(t *testing.T) { 98 x, _ := newACLXrpc(t) 99 seedMembers(t, x, aclSubject) 100 rec := httptest.NewRecorder() 101 x.ListMembers(rec, listRequest(t, url.Values{"limit": {"5000"}})) 102 if rec.Code != http.StatusOK { 103 t.Fatalf("limit=5000 should clamp and return 200, got %d; body=%s", rec.Code, rec.Body.String()) 104 } 105 if len(decodeMembers(t, rec).Items) != 1 { 106 t.Error("clamped limit must still return the seeded member") 107 } 108} 109 110func TestListMembers_PaginatesWithoutOverlap(t *testing.T) { 111 x, _ := newACLXrpc(t) 112 all := []string{"did:plc:scallop", "did:plc:whelk", "did:plc:limpet"} 113 seedMembers(t, x, all...) 114 115 seen := map[string]bool{} 116 params := url.Values{"subject": {"knot.example"}, "limit": {"2"}} 117 118 rec := httptest.NewRecorder() 119 x.ListMembers(rec, listRequest(t, params)) 120 page1 := decodeMembers(t, rec) 121 if len(page1.Items) != 2 || page1.Cursor == nil { 122 t.Fatalf("page1 items=%d cursor=%v, want 2 items and a cursor", len(page1.Items), page1.Cursor) 123 } 124 for _, it := range page1.Items { 125 seen[it.Subject] = true 126 } 127 128 params.Set("cursor", *page1.Cursor) 129 rec = httptest.NewRecorder() 130 x.ListMembers(rec, listRequest(t, params)) 131 page2 := decodeMembers(t, rec) 132 if len(page2.Items) != 1 { 133 t.Fatalf("page2 items = %d, want 1", len(page2.Items)) 134 } 135 if page2.Cursor != nil { 136 t.Errorf("page2 cursor = %q, want nil at end", *page2.Cursor) 137 } 138 for _, it := range page2.Items { 139 if seen[it.Subject] { 140 t.Errorf("subject %s repeated across pages", it.Subject) 141 } 142 seen[it.Subject] = true 143 } 144 145 if len(seen) != len(all) { 146 t.Errorf("distinct subjects seen = %d, want %d", len(seen), len(all)) 147 } 148} 149 150func TestListCollaborators_ScopedToRepo(t *testing.T) { 151 x, _ := newACLXrpc(t) 152 seedRepo(t, x) 153 154 addRec := httptest.NewRecorder() 155 x.AddCollaborator(addRec, aclRequest(t, aclOwner, tangled.RepoAddCollaborator_Input{Repo: aclRepoDid, Subject: aclSubject})) 156 if addRec.Code != http.StatusOK { 157 t.Fatalf("seed collaborator: status %d, body=%s", addRec.Code, addRec.Body.String()) 158 } 159 160 rec := httptest.NewRecorder() 161 x.ListCollaborators(rec, listRequest(t, url.Values{"subject": {aclRepoDid}})) 162 if rec.Code != http.StatusOK { 163 t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) 164 } 165 166 var out tangled.RepoListCollaborators_Output 167 if err := json.NewDecoder(rec.Body).Decode(&out); err != nil { 168 t.Fatalf("decode: %v", err) 169 } 170 if len(out.Items) != 1 { 171 t.Fatalf("items = %d, want 1", len(out.Items)) 172 } 173 if out.Items[0].Subject != aclSubject { 174 t.Errorf("subject = %q, want %s", out.Items[0].Subject, aclSubject) 175 } 176 if out.Items[0].AddedBy != aclOwner { 177 t.Errorf("addedBy = %q, want %s", out.Items[0].AddedBy, aclOwner) 178 } 179 if out.Items[0].Uri != nil || out.Items[0].Cid != nil { 180 t.Error("collaborator carries record uri/cid; a knot must omit them") 181 } 182} 183 184func TestListCollaborators_MalformedSubjectBadRequest(t *testing.T) { 185 x, _ := newACLXrpc(t) 186 rec := httptest.NewRecorder() 187 x.ListCollaborators(rec, listRequest(t, url.Values{"subject": {"notadid"}})) 188 if rec.Code != http.StatusBadRequest { 189 t.Errorf("status = %d, want 400", rec.Code) 190 } 191} 192 193func TestListCollaborators_UnknownRepoEmpty(t *testing.T) { 194 x, _ := newACLXrpc(t) 195 rec := httptest.NewRecorder() 196 x.ListCollaborators(rec, listRequest(t, url.Values{"subject": {"did:plc:scallop"}})) 197 if rec.Code != http.StatusOK { 198 t.Fatalf("status = %d, want 200", rec.Code) 199 } 200 var out tangled.RepoListCollaborators_Output 201 if err := json.NewDecoder(rec.Body).Decode(&out); err != nil { 202 t.Fatalf("decode: %v", err) 203 } 204 if len(out.Items) != 0 { 205 t.Errorf("items = %d, want 0 for an unknown repo", len(out.Items)) 206 } 207}