oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "context"
5 "errors"
6 "regexp"
7 "strconv"
8 "strings"
9 "testing"
10 "time"
11)
12
13// itoa is the test package's own decimal formatter, used only to spell
14// out the actor string an assertion expects.
15func itoa(id int64) string { return strconv.FormatInt(id, 10) }
16
17var agentTokenBodyRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
18
19func TestAgentTokenIsShownOnceAndStoredHashed(t *testing.T) {
20 s, _ := testStore(t)
21 ctx := context.Background()
22 op, err := s.ensurePerson(ctx, "paul@example.com")
23 if err != nil {
24 t.Fatal(err)
25 }
26 ag, token, err := s.createAgent(ctx, "claude", op)
27 if err != nil {
28 t.Fatal(err)
29 }
30 // The token's own value must never appear in a failure message —
31 // only its shape does (fix round 1, item 6).
32 if !strings.HasPrefix(token, "mt_") {
33 t.Fatal("token lacks the mt_ prefix")
34 }
35 if len(token) != 46 {
36 t.Fatalf("token length %d, want 46", len(token))
37 }
38 if !agentTokenBodyRe.MatchString(token[3:]) {
39 t.Fatal("token body after mt_ is not [A-Za-z0-9_-]+")
40 }
41 if ag.TokenHash != hashAgentToken(token) || len(ag.TokenHash) != 64 {
42 t.Fatalf("stored hash %q does not match the minted token's hash", ag.TokenHash)
43 }
44 if ag.TokenHash == token {
45 t.Fatal("the hash equals the plaintext token")
46 }
47
48 // Two mints never coincide.
49 token2, _, err := newAgentToken()
50 if err != nil {
51 t.Fatal(err)
52 }
53 if token2 == token {
54 t.Fatal("two mints produced the same token")
55 }
56
57 got, opGot, ok, err := s.agentByToken(ctx, token)
58 if err != nil || !ok || got.ID != ag.ID || opGot.ID != op.ID {
59 t.Fatalf("lookup: ok=%v err=%v got.ID=%d opGot.ID=%d", ok, err, got.ID, opGot.ID)
60 }
61 if _, _, ok, _ := s.agentByToken(ctx, "mt_nope"); ok {
62 t.Fatal("an unknown token must not resolve")
63 }
64 if principal(ag, op) != "claude~paul@example.com" {
65 t.Fatalf("principal %q", principal(ag, op))
66 }
67 if actorForAgent(ag) != "agent:"+itoa(ag.ID) {
68 t.Fatalf("actor %q", actorForAgent(ag))
69 }
70}
71
72// Fix round 1, item 1: resolving an agent whose operator row no longer
73// exists must fail closed (false, no error, indistinguishable from an
74// unknown token) rather than fall back to a bare-ID Person.
75func TestAgentByTokenFailsClosedWhenTheOperatorRowIsGone(t *testing.T) {
76 s, _ := testStore(t)
77 ctx := context.Background()
78 op, err := s.ensurePerson(ctx, "ghost@example.com")
79 if err != nil {
80 t.Fatal(err)
81 }
82 ag, token, err := s.createAgent(ctx, "claude", op)
83 if err != nil {
84 t.Fatal(err)
85 }
86 if err := s.g.WithContext(ctx).Where("id = ?", op.ID).Delete(&Person{}).Error; err != nil {
87 t.Fatal(err)
88 }
89 if _, _, ok, err := s.agentByToken(ctx, token); ok || err != nil {
90 t.Fatalf("agentByToken with a deleted operator: ok=%v err=%v", ok, err)
91 }
92 if _, _, ok, err := s.agentByID(ctx, ag.ID, op); ok || err != nil {
93 t.Fatalf("agentByID with a deleted operator: ok=%v err=%v", ok, err)
94 }
95}
96
97func TestRevokedAgentDoesNotResolveAndOnlyTheOperatorMayRevoke(t *testing.T) {
98 s, _ := testStore(t)
99 ctx := context.Background()
100 op, err := s.ensurePerson(ctx, "paul@example.com")
101 if err != nil {
102 t.Fatal(err)
103 }
104 other, err := s.ensurePerson(ctx, "bea@example.com")
105 if err != nil {
106 t.Fatal(err)
107 }
108 ag, token, _ := s.createAgent(ctx, "claude", op)
109 if ok, _ := s.revokeAgent(ctx, ag.ID, other); ok {
110 t.Fatal("another person revoked the agent")
111 }
112 if ok, _ := s.revokeAgent(ctx, ag.ID, op); !ok {
113 t.Fatal("the operator could not revoke")
114 }
115 if ok, _ := s.revokeAgent(ctx, ag.ID, op); ok {
116 t.Fatal("revoking twice must report false")
117 }
118 if _, _, ok, _ := s.agentByToken(ctx, token); ok {
119 t.Fatal("a revoked token must not resolve")
120 }
121 list, _ := s.agentsFor(ctx, op)
122 if len(list) != 1 || list[0].RevokedAt == nil {
123 t.Fatalf("list %+v", list)
124 }
125 if list2, _ := s.agentsFor(ctx, other); len(list2) != 0 {
126 t.Fatal("another person's list must be empty")
127 }
128}
129
130// Fix round 1, item 2: agentByID is scoped to its operator, the same
131// 404 shape as every other owned row.
132func TestAgentByIDIsScopedToItsOperator(t *testing.T) {
133 s, _ := testStore(t)
134 ctx := context.Background()
135 op, _ := s.ensurePerson(ctx, "paul@example.com")
136 other, _ := s.ensurePerson(ctx, "bea@example.com")
137 ag, _, err := s.createAgent(ctx, "claude", op)
138 if err != nil {
139 t.Fatal(err)
140 }
141 if _, _, ok, err := s.agentByID(ctx, ag.ID, other); ok || err != nil {
142 t.Fatalf("another operator's agentByID: ok=%v err=%v", ok, err)
143 }
144 got, gotOp, ok, err := s.agentByID(ctx, ag.ID, op)
145 if err != nil || !ok || got.ID != ag.ID || gotOp.ID != op.ID {
146 t.Fatalf("the real operator's agentByID: got=%+v ok=%v err=%v", got, ok, err)
147 }
148 if _, _, ok, err := s.agentByID(ctx, 999999, op); ok || err != nil {
149 t.Fatalf("unknown id: ok=%v err=%v", ok, err)
150 }
151}
152
153// Fix round 1, item 6: mint two agents, revoke the older, and check
154// both that the list stays newest-first and that the revoked row is
155// still there (revoking removes nothing, only marks it).
156func TestAgentsForOrdersNewestFirstAndKeepsRevokedRows(t *testing.T) {
157 s, now := testStore(t)
158 ctx := context.Background()
159 op, _ := s.ensurePerson(ctx, "paul@example.com")
160 older, _, err := s.createAgent(ctx, "older", op)
161 if err != nil {
162 t.Fatal(err)
163 }
164 *now = now.Add(time.Minute)
165 newer, _, err := s.createAgent(ctx, "newer", op)
166 if err != nil {
167 t.Fatal(err)
168 }
169 if ok, err := s.revokeAgent(ctx, older.ID, op); !ok || err != nil {
170 t.Fatalf("revoke: ok=%v err=%v", ok, err)
171 }
172 list, err := s.agentsFor(ctx, op)
173 if err != nil {
174 t.Fatal(err)
175 }
176 if len(list) != 2 {
177 t.Fatalf("agentsFor returned %d rows, want 2", len(list))
178 }
179 if list[0].ID != newer.ID || list[1].ID != older.ID {
180 t.Fatalf("order = [%d, %d], want newest [%d] then oldest [%d]", list[0].ID, list[1].ID, newer.ID, older.ID)
181 }
182 if list[1].RevokedAt == nil {
183 t.Fatal("the revoked row is missing from the list")
184 }
185 if list[0].RevokedAt != nil {
186 t.Fatal("the live row was reported revoked")
187 }
188}
189
190func TestAgentNameRules(t *testing.T) {
191 s, _ := testStore(t)
192 ctx := context.Background()
193 op := Person{ID: 1, Email: "paul@example.com"}
194 for _, bad := range []string{"", " ", "with space", "tilde~x", "colon:x", strings.Repeat("a", 65), "é"} {
195 if _, _, err := s.createAgent(ctx, bad, op); err == nil {
196 t.Fatalf("accepted name %q", bad)
197 }
198 }
199 if _, _, err := s.createAgent(ctx, " claude-2_ok ", op); err != nil {
200 t.Fatalf("trimmed valid name refused: %v", err)
201 }
202}
203
204// Fix round 1, item 3: a zero Person is never a legitimate operator.
205func TestCreateAgentRefusesAZeroOperator(t *testing.T) {
206 s, _ := testStore(t)
207 ctx := context.Background()
208 if _, _, err := s.createAgent(ctx, "claude", Person{}); err == nil {
209 t.Fatal("created an agent for a zero-value operator")
210 }
211}
212
213// Fix round 1, item 4: the cap lives in the store now, as
214// errAgentLimit; revoked agents never count against it.
215func TestCreateAgentRefusesTheTwentyFirstLiveAgent(t *testing.T) {
216 s, _ := testStore(t)
217 ctx := context.Background()
218 op, _ := s.ensurePerson(ctx, "paul@example.com")
219 for i := 0; i < maxLiveAgents; i++ {
220 if _, _, err := s.createAgent(ctx, "agent"+itoa(int64(i)), op); err != nil {
221 t.Fatalf("create agent %d: %v", i, err)
222 }
223 }
224 if _, _, err := s.createAgent(ctx, "one-too-many", op); !errors.Is(err, errAgentLimit) {
225 t.Fatalf("21st create returned %v, want errAgentLimit", err)
226 }
227 list, _ := s.agentsFor(ctx, op)
228 if len(list) != maxLiveAgents {
229 t.Fatalf("agent count after the refused create: %d, want %d", len(list), maxLiveAgents)
230 }
231
232 // Revoking one frees a slot.
233 if ok, _ := s.revokeAgent(ctx, list[0].ID, op); !ok {
234 t.Fatal("revoke to free a slot failed")
235 }
236 if _, _, err := s.createAgent(ctx, "one-more", op); err != nil {
237 t.Fatalf("create after revoking one: %v", err)
238 }
239}
240
241// Fix round 1, item 5: a duplicate live name is a Go-side check, not a
242// unique index — so a revoked name is free to reuse.
243func TestCreateAgentRefusesADuplicateLiveNameButAllowsReuseAfterRevoke(t *testing.T) {
244 s, _ := testStore(t)
245 ctx := context.Background()
246 op, _ := s.ensurePerson(ctx, "paul@example.com")
247 first, _, err := s.createAgent(ctx, "scheduler", op)
248 if err != nil {
249 t.Fatal(err)
250 }
251 if _, _, err := s.createAgent(ctx, "scheduler", op); !errors.Is(err, errAgentDuplicate) {
252 t.Fatalf("duplicate create returned %v, want errAgentDuplicate", err)
253 }
254 // Another operator may use the same name freely.
255 other, _ := s.ensurePerson(ctx, "bea@example.com")
256 if _, _, err := s.createAgent(ctx, "scheduler", other); err != nil {
257 t.Fatalf("another operator's same name was refused: %v", err)
258 }
259 if ok, _ := s.revokeAgent(ctx, first.ID, op); !ok {
260 t.Fatal("revoke failed")
261 }
262 if _, _, err := s.createAgent(ctx, "scheduler", op); err != nil {
263 t.Fatalf("reuse after revoke was refused: %v", err)
264 }
265}
266