Monorepo for Tangled
tangled.org
1package xrpc
2
3import (
4 "encoding/json"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8
9 "tangled.org/core/api/tangled"
10 "tangled.org/core/eventstream"
11 "tangled.org/core/knotserver/db"
12)
13
14func eventsOfType(t *testing.T, x *Xrpc, nsid string) []eventstream.Event {
15 t.Helper()
16 all, err := x.Db.GetEvents(0, 1000)
17 if err != nil {
18 t.Fatalf("GetEvents: %v", err)
19 }
20 var out []eventstream.Event
21 for _, e := range all {
22 if e.Nsid == nsid {
23 out = append(out, e)
24 }
25 }
26 return out
27}
28
29func decodeMemberUpdate(t *testing.T, e eventstream.Event) db.KnotMemberUpdate {
30 t.Helper()
31 var m db.KnotMemberUpdate
32 if err := json.Unmarshal(e.EventJson, &m); err != nil {
33 t.Fatalf("decode memberUpdate: %v", err)
34 }
35 return m
36}
37
38func decodeCollaboratorUpdate(t *testing.T, e eventstream.Event) db.RepoCollaboratorUpdate {
39 t.Helper()
40 var c db.RepoCollaboratorUpdate
41 if err := json.Unmarshal(e.EventJson, &c); err != nil {
42 t.Fatalf("decode collaboratorUpdate: %v", err)
43 }
44 return c
45}
46
47func TestAddMember_EmitsAddEvent(t *testing.T) {
48 x, _ := newACLXrpc(t)
49 rec := httptest.NewRecorder()
50 x.AddMember(rec, aclRequest(t, aclOwner, tangled.KnotAddMember_Input{Subject: aclSubject}))
51 if rec.Code != http.StatusOK {
52 t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
53 }
54
55 evs := eventsOfType(t, x, db.KnotMemberUpdateNSID)
56 if len(evs) != 1 {
57 t.Fatalf("memberUpdate events = %d, want 1", len(evs))
58 }
59 m := decodeMemberUpdate(t, evs[0])
60 if m.Op != db.AclOpAdd || m.Subject != aclSubject {
61 t.Errorf("event = %+v, want op=add subject=%s", m, aclSubject)
62 }
63}
64
65func TestRemoveMember_EmitsRemoveEvent(t *testing.T) {
66 x, _ := newACLXrpc(t)
67 seedMembers(t, x, aclSubject)
68
69 rec := httptest.NewRecorder()
70 x.RemoveMember(rec, aclRequest(t, aclOwner, tangled.KnotRemoveMember_Input{Subject: aclSubject}))
71 if rec.Code != http.StatusOK {
72 t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
73 }
74
75 evs := eventsOfType(t, x, db.KnotMemberUpdateNSID)
76 if len(evs) != 2 {
77 t.Fatalf("memberUpdate events = %d, want 2 (add then remove)", len(evs))
78 }
79 last := decodeMemberUpdate(t, evs[len(evs)-1])
80 if last.Op != db.AclOpRemove || last.Subject != aclSubject {
81 t.Errorf("last event = %+v, want op=remove subject=%s", last, aclSubject)
82 }
83}
84
85func TestAddMember_NoOpDoesNotEmit(t *testing.T) {
86 x, _ := newACLXrpc(t)
87 for range 2 {
88 rec := httptest.NewRecorder()
89 x.AddMember(rec, aclRequest(t, aclOwner, tangled.KnotAddMember_Input{Subject: aclSubject}))
90 if rec.Code != http.StatusOK {
91 t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
92 }
93 }
94
95 if evs := eventsOfType(t, x, db.KnotMemberUpdateNSID); len(evs) != 1 {
96 t.Errorf("memberUpdate events = %d, want 1; a duplicate grant is a no-op and must not emit", len(evs))
97 }
98}
99
100func TestCollaborator_EmitsAddThenRemove(t *testing.T) {
101 x, _ := newACLXrpc(t)
102 seedRepo(t, x)
103
104 addRec := httptest.NewRecorder()
105 x.AddCollaborator(addRec, aclRequest(t, aclOwner, tangled.RepoAddCollaborator_Input{Repo: aclRepoDid, Subject: aclSubject}))
106 if addRec.Code != http.StatusOK {
107 t.Fatalf("add status = %d, body=%s", addRec.Code, addRec.Body.String())
108 }
109
110 rmRec := httptest.NewRecorder()
111 x.RemoveCollaborator(rmRec, aclRequest(t, aclOwner, tangled.RepoRemoveCollaborator_Input{Repo: aclRepoDid, Subject: aclSubject}))
112 if rmRec.Code != http.StatusOK {
113 t.Fatalf("remove status = %d, body=%s", rmRec.Code, rmRec.Body.String())
114 }
115
116 evs := eventsOfType(t, x, db.RepoCollaboratorUpdateNSID)
117 if len(evs) != 2 {
118 t.Fatalf("collaboratorUpdate events = %d, want 2 (add then remove)", len(evs))
119 }
120
121 add := decodeCollaboratorUpdate(t, evs[0])
122 if add.Op != db.AclOpAdd || add.Subject != aclSubject || add.Repo != aclRepoDid {
123 t.Errorf("add event = %+v, want op=add subject=%s repo=%s", add, aclSubject, aclRepoDid)
124 }
125 rm := decodeCollaboratorUpdate(t, evs[1])
126 if rm.Op != db.AclOpRemove || rm.Subject != aclSubject || rm.Repo != aclRepoDid {
127 t.Errorf("remove event = %+v, want op=remove subject=%s repo=%s", rm, aclSubject, aclRepoDid)
128 }
129}