tinyrelayfile
tinyrelay/internal/daemon/grant_approvals_test.go/

internal/daemon/grant_approvals_test.go raw
1package daemon
2
3import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/FelineStateMachine/tinyrelay/internal/community"
11 "github.com/FelineStateMachine/tinyrelay/internal/event"
12)
13
14func approvalGrantRequest(t *testing.T, grant event.Event, createdAt int64) event.Event {
15 t.Helper()
16 raw, err := json.Marshal(community.AgentGrantRequest{Base: grant.ID, Changes: community.AgentGrantChanges{Kinds: []int{30617, 30618}, Repos: []community.AgentRepo{{Owner: event.Tag(grant, "d"), Identifier: "seedmark", Level: "maintain"}}}})
17 if err != nil {
18 t.Fatal(err)
19 }
20 return signedEvent(t, testAgentSecret, 1111, createdAt, [][]string{{"request", "grant"}, {"grant", string(raw)}, {"E", grant.ID, "", grant.PubKey}, {"K", "30392"}, {"P", grant.PubKey}, {"e", grant.ID, "", grant.PubKey}, {"k", "30392"}, {"p", grant.PubKey}}, "Publish the Seedmark repository and its hosting manifest.")
21}
22
23func TestGrantApprovalAppliesAndRetainsDecision(t *testing.T) {
24 _, tenant := testTenant(t)
25 ctx := context.Background()
26 now := time.Now().Unix()
27 agent, _ := event.PublicKey(testAgentSecret)
28 grant := agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"}, []string{"room", tenant.meta.Name}, []string{"sites", "*"})
29 if err := publishAs(t, tenant, grant); err != nil {
30 t.Fatal(err)
31 }
32 request := approvalGrantRequest(t, grant, now-20)
33 if err := publishAs(t, tenant, request); err != nil {
34 t.Fatalf("request with no comment scope: %v", err)
35 }
36 detail, err := approvalCall(t, tenant, grant.PubKey, "browseapproval", map[string]any{"id": request.ID})
37 if err != nil {
38 t.Fatal(err)
39 }
40 item := detail["item"].(map[string]any)
41 if item["type"] != "grant" || item["state"] != "open" || item["grant"] == nil {
42 t.Fatalf("missing actionable review: %v", item)
43 }
44 for _, actor := range []string{agent, grant.PubKey} {
45 if _, err := approvalCall(t, tenant, actor, "browseapproval", map[string]any{"id": request.ID}); err != nil {
46 t.Fatalf("participant cannot read review: %v", err)
47 }
48 }
49 if _, err := approvalCall(t, tenant, wikiKey(t, approvalOtherSecret), "browseapproval", map[string]any{"id": request.ID}); err == nil {
50 t.Fatal("stranger read grant request")
51 }
52 // A reaction alone must never make the UI claim access was granted.
53 positive := signedEvent(t, testOwnerSecret, 7, now-10, [][]string{{"e", request.ID}, {"p", agent}}, "+")
54 if err := publishAs(t, tenant, positive); err != nil {
55 t.Fatal(err)
56 }
57 detail, err = approvalCall(t, tenant, grant.PubKey, "browseapproval", map[string]any{"id": request.ID})
58 if err != nil || detail["item"].(map[string]any)["state"] != "open" {
59 t.Fatalf("reaction settled an unapplied grant: %v %v", detail, err)
60 }
61 review, err := tenant.community.ReviewAgentGrantRequest(ctx, request, now)
62 if err != nil {
63 t.Fatal(err)
64 }
65 replacement := review.Unsigned
66 if err := event.Sign(&replacement, testOwnerSecret); err != nil {
67 t.Fatal(err)
68 }
69 if err := publishAs(t, tenant, replacement); err != nil {
70 t.Fatalf("reviewed replacement rejected: %v", err)
71 }
72 current, found, err := tenant.community.AgentGrant(ctx, agent)
73 if err != nil || !found || !containsInt(current.Scope.Kinds, 30617) || len(current.Scope.Sites) != 1 {
74 t.Fatalf("grant not applied or existing site lost: %+v %v", current, err)
75 }
76 // A later ordinary edit must not erase the approval's receipt.
77 manual := agentGrantEvent(t, agent, now+1, now+3600, []string{"k", "9"})
78 if err := publishAs(t, tenant, manual); err != nil {
79 t.Fatal(err)
80 }
81 for _, method := range []string{"browseapproval", "browseapprovals"} {
82 result, err := approvalCall(t, tenant, grant.PubKey, method, map[string]any{"id": request.ID})
83 if err != nil {
84 t.Fatal(err)
85 }
86 got := result["item"]
87 if method == "browseapprovals" {
88 got = approvalByID(wikiItems(result, "items"), request.ID)
89 }
90 item := got.(map[string]any)
91 answer := item["answer"].(map[string]any)
92 if item["state"] != "answered" || answer["decision"] != "approved" || answer["id"] != replacement.ID {
93 t.Fatalf("lost grant approval history: %v", item)
94 }
95 }
96}
97
98func TestGrantApprovalDenialAndStaleReview(t *testing.T) {
99 for _, scenario := range []string{"deny", "supersede"} {
100 t.Run(scenario, func(t *testing.T) {
101 _, tenant := testTenant(t)
102 now := time.Now().Unix()
103 agent, _ := event.PublicKey(testAgentSecret)
104 grant := agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"})
105 if err := publishAs(t, tenant, grant); err != nil {
106 t.Fatal(err)
107 }
108 request := approvalGrantRequest(t, grant, now-20)
109 if err := publishAs(t, tenant, request); err != nil {
110 t.Fatal(err)
111 }
112 review, err := tenant.community.ReviewAgentGrantRequest(context.Background(), request, now)
113 if err != nil {
114 t.Fatal(err)
115 }
116 if scenario == "deny" {
117 if err := publishAs(t, tenant, signedEvent(t, testOwnerSecret, 7, now-10, [][]string{{"e", request.ID}, {"p", agent}}, "\n-\t")); err != nil {
118 t.Fatal(err)
119 }
120 } else if err := publishAs(t, tenant, agentGrantEvent(t, agent, now-5, now+3600, []string{"k", "1"})); err != nil {
121 t.Fatal(err)
122 }
123 replacement := review.Unsigned
124 if err := event.Sign(&replacement, testOwnerSecret); err != nil {
125 t.Fatal(err)
126 }
127 if err := publishAs(t, tenant, replacement); err == nil {
128 t.Fatal("obsolete request changed the grant")
129 }
130 detail, err := approvalCall(t, tenant, grant.PubKey, "browseapproval", map[string]any{"id": request.ID})
131 if err != nil {
132 t.Fatal(err)
133 }
134 item := detail["item"].(map[string]any)
135 if item["grant"] != nil {
136 t.Fatalf("inactive request still actionable: %v", item)
137 }
138 if scenario == "deny" && (item["state"] != "answered" || item["answer"].(map[string]any)["decision"] != "denied") {
139 t.Fatalf("denial not shown: %v", item)
140 }
141 if scenario == "supersede" && (item["state"] != "expired" || !strings.HasPrefix(item["grant_error"].(string), "conflict:")) {
142 t.Fatalf("stale review not marked: %v", item)
143 }
144 })
145 }
146}
147
148func TestGrantRequestNotificationRequiresReview(t *testing.T) {
149 _, tenant := testTenant(t)
150 now := time.Now().Unix()
151 agent, _ := event.PublicKey(testAgentSecret)
152 request := approvalGrantRequest(t, agentGrantEvent(t, agent, now-30, now+3600, []string{"k", "9"}), now-20)
153 notices := tenant.pushNotices(context.Background(), request)
154 if len(notices) != 1 || notices[0].recipient != tenant.Policy().Owner || notices[0].category != pushApprovals || len(notices[0].actions) != 1 || notices[0].actions[0].Action != "review" || !strings.Contains(notices[0].url, "/approvals?id="+request.ID) {
155 t.Fatalf("grant request notification: %+v", notices)
156 }
157}
158
159func TestGrantReviewReplacementAdvancesTimestamp(t *testing.T) {
160 _, tenant := testTenant(t)
161 now := time.Now().Unix()
162 agent, _ := event.PublicKey(testAgentSecret)
163 grant := agentGrantEvent(t, agent, now, now+3600, []string{"k", "9"})
164 if err := publishAs(t, tenant, grant); err != nil {
165 t.Fatal(err)
166 }
167 request := approvalGrantRequest(t, grant, now)
168 review, err := tenant.community.ReviewAgentGrantRequest(context.Background(), request, now)
169 if err != nil {
170 t.Fatal(err)
171 }
172 if review.Unsigned.CreatedAt <= grant.CreatedAt {
173 t.Fatal("replacement can lose the same-second event ordering tie")
174 }
175}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs