follows_test.go raw
1 package policy
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9
10 "github.com/adrg/xdg"
11 "next.orly.dev/pkg/nostr/encoders/hex"
12 )
13
14 // setupTestPolicy creates a policy manager with a temporary config file.
15 // Returns the policy and a cleanup function.
16 func setupTestPolicy(t *testing.T, appName string) (*P, func()) {
17 t.Helper()
18
19 // Create config directory at XDG path
20 configDir := filepath.Join(xdg.ConfigHome, appName)
21 if err := os.MkdirAll(configDir, 0755); err != nil {
22 t.Fatalf("Failed to create config dir: %v", err)
23 }
24
25 // Create default policy.json
26 configPath := filepath.Join(configDir, "policy.json")
27 defaultPolicy := []byte(`{"default_policy": "allow"}`)
28 if err := os.WriteFile(configPath, defaultPolicy, 0644); err != nil {
29 t.Fatalf("Failed to write policy file: %v", err)
30 }
31
32 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
33
34 policy := NewWithManager(ctx, appName, true, "")
35 if policy == nil {
36 cancel()
37 os.RemoveAll(configDir)
38 t.Fatal("Failed to create policy manager")
39 }
40
41 cleanup := func() {
42 cancel()
43 os.RemoveAll(configDir)
44 }
45
46 return policy, cleanup
47 }
48
49 // TestIsPolicyAdmin tests the IsPolicyAdmin method
50 func TestIsPolicyAdmin(t *testing.T) {
51 policy, cleanup := setupTestPolicy(t, "test-policy-admin")
52 defer cleanup()
53
54 // Set up policy with admins
55 admin1Hex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
56 admin2Hex := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
57 nonAdminHex := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
58
59 policyJSON := []byte(`{
60 "policy_admins": [
61 "` + admin1Hex + `",
62 "` + admin2Hex + `"
63 ]
64 }`)
65
66 tmpDir := t.TempDir()
67 if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
68 t.Fatalf("Failed to reload policy: %v", err)
69 }
70
71 // Convert hex to bytes for testing
72 admin1Bin, _ := hex.Dec(admin1Hex)
73 admin2Bin, _ := hex.Dec(admin2Hex)
74 nonAdminBin, _ := hex.Dec(nonAdminHex)
75
76 tests := []struct {
77 name string
78 pubkey []byte
79 expected bool
80 }{
81 {
82 name: "first admin is recognized",
83 pubkey: admin1Bin,
84 expected: true,
85 },
86 {
87 name: "second admin is recognized",
88 pubkey: admin2Bin,
89 expected: true,
90 },
91 {
92 name: "non-admin is not recognized",
93 pubkey: nonAdminBin,
94 expected: false,
95 },
96 {
97 name: "nil pubkey returns false",
98 pubkey: nil,
99 expected: false,
100 },
101 {
102 name: "empty pubkey returns false",
103 pubkey: []byte{},
104 expected: false,
105 },
106 {
107 name: "wrong length pubkey returns false",
108 pubkey: []byte{0x01, 0x02, 0x03},
109 expected: false,
110 },
111 }
112
113 for _, tt := range tests {
114 t.Run(tt.name, func(t *testing.T) {
115 result := policy.IsPolicyAdmin(tt.pubkey)
116 if result != tt.expected {
117 t.Errorf("IsPolicyAdmin() = %v, expected %v", result, tt.expected)
118 }
119 })
120 }
121 }
122
123 // TestIsPolicyFollow tests the IsPolicyFollow method
124 func TestIsPolicyFollow(t *testing.T) {
125 policy, cleanup := setupTestPolicy(t, "test-policy-follow")
126 defer cleanup()
127
128 // Set up some follows
129 follow1Hex := "1111111111111111111111111111111111111111111111111111111111111111"
130 follow2Hex := "2222222222222222222222222222222222222222222222222222222222222222"
131 nonFollowHex := "3333333333333333333333333333333333333333333333333333333333333333"
132
133 follow1Bin, _ := hex.Dec(follow1Hex)
134 follow2Bin, _ := hex.Dec(follow2Hex)
135 nonFollowBin, _ := hex.Dec(nonFollowHex)
136
137 // Update policy follows directly
138 policy.UpdatePolicyFollows([][]byte{follow1Bin, follow2Bin})
139
140 tests := []struct {
141 name string
142 pubkey []byte
143 expected bool
144 }{
145 {
146 name: "first follow is recognized",
147 pubkey: follow1Bin,
148 expected: true,
149 },
150 {
151 name: "second follow is recognized",
152 pubkey: follow2Bin,
153 expected: true,
154 },
155 {
156 name: "non-follow is not recognized",
157 pubkey: nonFollowBin,
158 expected: false,
159 },
160 {
161 name: "nil pubkey returns false",
162 pubkey: nil,
163 expected: false,
164 },
165 {
166 name: "empty pubkey returns false",
167 pubkey: []byte{},
168 expected: false,
169 },
170 }
171
172 for _, tt := range tests {
173 t.Run(tt.name, func(t *testing.T) {
174 result := policy.IsPolicyFollow(tt.pubkey)
175 if result != tt.expected {
176 t.Errorf("IsPolicyFollow() = %v, expected %v", result, tt.expected)
177 }
178 })
179 }
180 }
181
182 // TestUpdatePolicyFollows tests the UpdatePolicyFollows method
183 func TestUpdatePolicyFollows(t *testing.T) {
184 policy, cleanup := setupTestPolicy(t, "test-update-follows")
185 defer cleanup()
186
187 // Initially no follows
188 testPubkey, _ := hex.Dec("1111111111111111111111111111111111111111111111111111111111111111")
189 if policy.IsPolicyFollow(testPubkey) {
190 t.Error("Expected no follows initially")
191 }
192
193 // Add follows
194 follows := [][]byte{testPubkey}
195 policy.UpdatePolicyFollows(follows)
196
197 if !policy.IsPolicyFollow(testPubkey) {
198 t.Error("Expected pubkey to be a follow after update")
199 }
200
201 // Update with empty list
202 policy.UpdatePolicyFollows([][]byte{})
203 if policy.IsPolicyFollow(testPubkey) {
204 t.Error("Expected pubkey to not be a follow after clearing")
205 }
206
207 // Update with nil
208 policy.UpdatePolicyFollows(nil)
209 if policy.IsPolicyFollow(testPubkey) {
210 t.Error("Expected pubkey to not be a follow after nil update")
211 }
212 }
213
214 // TestIsPolicyFollowWhitelistEnabled tests the IsPolicyFollowWhitelistEnabled method
215 func TestIsPolicyFollowWhitelistEnabled(t *testing.T) {
216 policy, cleanup := setupTestPolicy(t, "test-whitelist-enabled")
217 defer cleanup()
218
219 tmpDir := t.TempDir()
220
221 // Test with disabled
222 policyJSON := []byte(`{"policy_follow_whitelist_enabled": false}`)
223 if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
224 t.Fatalf("Failed to reload policy: %v", err)
225 }
226
227 if policy.IsPolicyFollowWhitelistEnabled() {
228 t.Error("Expected follow whitelist to be disabled")
229 }
230
231 // Test with enabled
232 policyJSON = []byte(`{"policy_follow_whitelist_enabled": true}`)
233 if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
234 t.Fatalf("Failed to reload policy: %v", err)
235 }
236
237 if !policy.IsPolicyFollowWhitelistEnabled() {
238 t.Error("Expected follow whitelist to be enabled")
239 }
240 }
241
242 // TestGetPolicyAdminsBin tests the GetPolicyAdminsBin method
243 func TestGetPolicyAdminsBin(t *testing.T) {
244 policy, cleanup := setupTestPolicy(t, "test-get-admins-bin")
245 defer cleanup()
246
247 admin1Hex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
248 admin2Hex := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
249
250 policyJSON := []byte(`{
251 "policy_admins": ["` + admin1Hex + `", "` + admin2Hex + `"]
252 }`)
253
254 tmpDir := t.TempDir()
255 if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
256 t.Fatalf("Failed to reload policy: %v", err)
257 }
258
259 admins := policy.GetPolicyAdminsBin()
260 if len(admins) != 2 {
261 t.Errorf("Expected 2 admins, got %d", len(admins))
262 }
263
264 // Verify it's a copy (modification shouldn't affect original)
265 if len(admins) > 0 {
266 admins[0][0] = 0xFF
267 originalAdmins := policy.GetPolicyAdminsBin()
268 if originalAdmins[0][0] == 0xFF {
269 t.Error("GetPolicyAdminsBin should return a copy, not the original slice")
270 }
271 }
272 }
273
274 // TestFollowListConcurrency tests concurrent access to follow list
275 func TestFollowListConcurrency(t *testing.T) {
276 policy, cleanup := setupTestPolicy(t, "test-concurrency")
277 defer cleanup()
278
279 testPubkey, _ := hex.Dec("1111111111111111111111111111111111111111111111111111111111111111")
280
281 // Run concurrent reads and writes
282 done := make(chan bool)
283 for i := 0; i < 10; i++ {
284 go func() {
285 for j := 0; j < 100; j++ {
286 policy.UpdatePolicyFollows([][]byte{testPubkey})
287 _ = policy.IsPolicyFollow(testPubkey)
288 _ = policy.IsPolicyAdmin(testPubkey)
289 }
290 done <- true
291 }()
292 }
293
294 // Wait for all goroutines
295 for i := 0; i < 10; i++ {
296 <-done
297 }
298 }
299
300 // TestPolicyAdminAndFollowInteraction tests the interaction between admin and follow checks
301 func TestPolicyAdminAndFollowInteraction(t *testing.T) {
302 policy, cleanup := setupTestPolicy(t, "test-admin-follow-interaction")
303 defer cleanup()
304
305 // An admin who is also followed
306 adminHex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
307 adminBin, _ := hex.Dec(adminHex)
308
309 policyJSON := []byte(`{
310 "policy_admins": ["` + adminHex + `"],
311 "policy_follow_whitelist_enabled": true
312 }`)
313
314 tmpDir := t.TempDir()
315 if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
316 t.Fatalf("Failed to reload policy: %v", err)
317 }
318
319 // Admin should be recognized as admin
320 if !policy.IsPolicyAdmin(adminBin) {
321 t.Error("Expected admin to be recognized as admin")
322 }
323
324 // Admin is not automatically a follow
325 if policy.IsPolicyFollow(adminBin) {
326 t.Error("Admin should not automatically be a follow")
327 }
328
329 // Now add admin as a follow
330 policy.UpdatePolicyFollows([][]byte{adminBin})
331
332 // Should be both admin and follow
333 if !policy.IsPolicyAdmin(adminBin) {
334 t.Error("Expected admin to still be recognized as admin")
335 }
336 if !policy.IsPolicyFollow(adminBin) {
337 t.Error("Expected admin to now be recognized as follow")
338 }
339 }
340