nip43_test.go raw
1 package database
2
3 import (
4 "context"
5 "os"
6 "testing"
7 "time"
8 )
9
10 func setupNIP43TestDB(t *testing.T) (*D, func()) {
11 tempDir, err := os.MkdirTemp("", "nip43_test_*")
12 if err != nil {
13 t.Fatalf("failed to create temp dir: %v", err)
14 }
15
16 ctx, cancel := context.WithCancel(context.Background())
17 db, err := New(ctx, cancel, tempDir, "info")
18 if err != nil {
19 os.RemoveAll(tempDir)
20 t.Fatalf("failed to open database: %v", err)
21 }
22
23 cleanup := func() {
24 db.Close()
25 os.RemoveAll(tempDir)
26 }
27
28 return db, cleanup
29 }
30
31 // TestAddNIP43Member tests adding a member
32 func TestAddNIP43Member(t *testing.T) {
33 db, cleanup := setupNIP43TestDB(t)
34 defer cleanup()
35
36 pubkey := make([]byte, 32)
37 for i := range pubkey {
38 pubkey[i] = byte(i)
39 }
40 inviteCode := "test-invite-123"
41
42 err := db.AddNIP43Member(pubkey, inviteCode)
43 if err != nil {
44 t.Fatalf("failed to add member: %v", err)
45 }
46
47 // Verify member was added
48 isMember, err := db.IsNIP43Member(pubkey)
49 if err != nil {
50 t.Fatalf("failed to check membership: %v", err)
51 }
52 if !isMember {
53 t.Error("member was not added")
54 }
55 }
56
57 // TestAddNIP43Member_InvalidPubkey tests adding member with invalid pubkey
58 func TestAddNIP43Member_InvalidPubkey(t *testing.T) {
59 db, cleanup := setupNIP43TestDB(t)
60 defer cleanup()
61
62 // Test with wrong length
63 invalidPubkey := make([]byte, 16)
64 err := db.AddNIP43Member(invalidPubkey, "test-code")
65 if err == nil {
66 t.Error("expected error for invalid pubkey length")
67 }
68 }
69
70 // TestRemoveNIP43Member tests removing a member
71 func TestRemoveNIP43Member(t *testing.T) {
72 db, cleanup := setupNIP43TestDB(t)
73 defer cleanup()
74
75 pubkey := make([]byte, 32)
76 for i := range pubkey {
77 pubkey[i] = byte(i)
78 }
79
80 // Add member
81 err := db.AddNIP43Member(pubkey, "test-code")
82 if err != nil {
83 t.Fatalf("failed to add member: %v", err)
84 }
85
86 // Remove member
87 err = db.RemoveNIP43Member(pubkey)
88 if err != nil {
89 t.Fatalf("failed to remove member: %v", err)
90 }
91
92 // Verify member was removed
93 isMember, err := db.IsNIP43Member(pubkey)
94 if err != nil {
95 t.Fatalf("failed to check membership: %v", err)
96 }
97 if isMember {
98 t.Error("member was not removed")
99 }
100 }
101
102 // TestIsNIP43Member tests membership checking
103 func TestIsNIP43Member(t *testing.T) {
104 db, cleanup := setupNIP43TestDB(t)
105 defer cleanup()
106
107 pubkey := make([]byte, 32)
108 for i := range pubkey {
109 pubkey[i] = byte(i)
110 }
111
112 // Check non-existent member
113 isMember, err := db.IsNIP43Member(pubkey)
114 if err != nil {
115 t.Fatalf("failed to check membership: %v", err)
116 }
117 if isMember {
118 t.Error("non-existent member reported as member")
119 }
120
121 // Add member
122 err = db.AddNIP43Member(pubkey, "test-code")
123 if err != nil {
124 t.Fatalf("failed to add member: %v", err)
125 }
126
127 // Check existing member
128 isMember, err = db.IsNIP43Member(pubkey)
129 if err != nil {
130 t.Fatalf("failed to check membership: %v", err)
131 }
132 if !isMember {
133 t.Error("existing member not found")
134 }
135 }
136
137 // TestGetNIP43Membership tests retrieving membership details
138 func TestGetNIP43Membership(t *testing.T) {
139 db, cleanup := setupNIP43TestDB(t)
140 defer cleanup()
141
142 pubkey := make([]byte, 32)
143 for i := range pubkey {
144 pubkey[i] = byte(i)
145 }
146 inviteCode := "test-invite-abc123"
147
148 // Add member
149 beforeAdd := time.Now()
150 err := db.AddNIP43Member(pubkey, inviteCode)
151 if err != nil {
152 t.Fatalf("failed to add member: %v", err)
153 }
154 afterAdd := time.Now()
155
156 // Get membership
157 membership, err := db.GetNIP43Membership(pubkey)
158 if err != nil {
159 t.Fatalf("failed to get membership: %v", err)
160 }
161
162 // Verify details
163 if len(membership.Pubkey) != 32 {
164 t.Errorf("wrong pubkey length: got %d, want 32", len(membership.Pubkey))
165 }
166 for i := range pubkey {
167 if membership.Pubkey[i] != pubkey[i] {
168 t.Errorf("pubkey mismatch at index %d", i)
169 break
170 }
171 }
172
173 if membership.InviteCode != inviteCode {
174 t.Errorf("invite code mismatch: got %s, want %s", membership.InviteCode, inviteCode)
175 }
176
177 // Allow some tolerance for timestamp (database operations may take time)
178 if membership.AddedAt.Before(beforeAdd.Add(-5*time.Second)) || membership.AddedAt.After(afterAdd.Add(5*time.Second)) {
179 t.Errorf("AddedAt timestamp out of expected range: got %v, expected between %v and %v",
180 membership.AddedAt, beforeAdd, afterAdd)
181 }
182 }
183
184 // TestGetAllNIP43Members tests retrieving all members
185 func TestGetAllNIP43Members(t *testing.T) {
186 db, cleanup := setupNIP43TestDB(t)
187 defer cleanup()
188
189 // Add multiple members
190 memberCount := 5
191 for i := 0; i < memberCount; i++ {
192 pubkey := make([]byte, 32)
193 for j := range pubkey {
194 pubkey[j] = byte(i*10 + j)
195 }
196 err := db.AddNIP43Member(pubkey, "code-"+string(rune(i)))
197 if err != nil {
198 t.Fatalf("failed to add member %d: %v", i, err)
199 }
200 }
201
202 // Get all members
203 members, err := db.GetAllNIP43Members()
204 if err != nil {
205 t.Fatalf("failed to get all members: %v", err)
206 }
207
208 if len(members) != memberCount {
209 t.Errorf("wrong member count: got %d, want %d", len(members), memberCount)
210 }
211
212 // Verify each member has valid pubkey
213 for i, member := range members {
214 if len(member) != 32 {
215 t.Errorf("member %d has invalid pubkey length: %d", i, len(member))
216 }
217 }
218 }
219
220 // TestStoreInviteCode tests storing invite codes
221 func TestStoreInviteCode(t *testing.T) {
222 db, cleanup := setupNIP43TestDB(t)
223 defer cleanup()
224
225 code := "test-invite-xyz789"
226 expiresAt := time.Now().Add(24 * time.Hour)
227
228 err := db.StoreInviteCode(code, expiresAt)
229 if err != nil {
230 t.Fatalf("failed to store invite code: %v", err)
231 }
232
233 // Validate the code
234 valid, err := db.ValidateInviteCode(code)
235 if err != nil {
236 t.Fatalf("failed to validate invite code: %v", err)
237 }
238 if !valid {
239 t.Error("stored invite code is not valid")
240 }
241 }
242
243 // TestValidateInviteCode_Expired tests expired invite code handling
244 func TestValidateInviteCode_Expired(t *testing.T) {
245 db, cleanup := setupNIP43TestDB(t)
246 defer cleanup()
247
248 code := "expired-code"
249 expiresAt := time.Now().Add(-1 * time.Hour) // Already expired
250
251 err := db.StoreInviteCode(code, expiresAt)
252 if err != nil {
253 t.Fatalf("failed to store invite code: %v", err)
254 }
255
256 // Validate the code - should be invalid because it's expired
257 valid, err := db.ValidateInviteCode(code)
258 if err != nil {
259 t.Fatalf("failed to validate invite code: %v", err)
260 }
261 if valid {
262 t.Error("expired invite code reported as valid")
263 }
264 }
265
266 // TestValidateInviteCode_NonExistent tests non-existent code validation
267 func TestValidateInviteCode_NonExistent(t *testing.T) {
268 db, cleanup := setupNIP43TestDB(t)
269 defer cleanup()
270
271 valid, err := db.ValidateInviteCode("non-existent-code")
272 if err != nil {
273 t.Fatalf("unexpected error: %v", err)
274 }
275 if valid {
276 t.Error("non-existent code reported as valid")
277 }
278 }
279
280 // TestDeleteInviteCode tests deleting invite codes
281 func TestDeleteInviteCode(t *testing.T) {
282 db, cleanup := setupNIP43TestDB(t)
283 defer cleanup()
284
285 code := "delete-me-code"
286 expiresAt := time.Now().Add(24 * time.Hour)
287
288 // Store code
289 err := db.StoreInviteCode(code, expiresAt)
290 if err != nil {
291 t.Fatalf("failed to store invite code: %v", err)
292 }
293
294 // Verify it exists
295 valid, err := db.ValidateInviteCode(code)
296 if err != nil {
297 t.Fatalf("failed to validate invite code: %v", err)
298 }
299 if !valid {
300 t.Error("stored code is not valid")
301 }
302
303 // Delete code
304 err = db.DeleteInviteCode(code)
305 if err != nil {
306 t.Fatalf("failed to delete invite code: %v", err)
307 }
308
309 // Verify it's gone
310 valid, err = db.ValidateInviteCode(code)
311 if err != nil {
312 t.Fatalf("failed to validate after delete: %v", err)
313 }
314 if valid {
315 t.Error("deleted code still valid")
316 }
317 }
318
319 // TestNIP43Membership_Serialization tests membership serialization
320 func TestNIP43Membership_Serialization(t *testing.T) {
321 pubkey := make([]byte, 32)
322 for i := range pubkey {
323 pubkey[i] = byte(i)
324 }
325
326 original := NIP43Membership{
327 Pubkey: pubkey,
328 AddedAt: time.Now(),
329 InviteCode: "test-code-123",
330 }
331
332 // Serialize
333 data := serializeNIP43Membership(original)
334
335 // Deserialize
336 deserialized := deserializeNIP43Membership(data)
337
338 // Verify
339 if deserialized == nil {
340 t.Fatal("deserialization returned nil")
341 }
342
343 if len(deserialized.Pubkey) != 32 {
344 t.Errorf("wrong pubkey length: got %d, want 32", len(deserialized.Pubkey))
345 }
346
347 for i := range pubkey {
348 if deserialized.Pubkey[i] != pubkey[i] {
349 t.Errorf("pubkey mismatch at index %d", i)
350 break
351 }
352 }
353
354 if deserialized.InviteCode != original.InviteCode {
355 t.Errorf("invite code mismatch: got %s, want %s", deserialized.InviteCode, original.InviteCode)
356 }
357
358 // Allow 1 second tolerance for timestamp comparison (due to Unix conversion)
359 timeDiff := deserialized.AddedAt.Sub(original.AddedAt)
360 if timeDiff < -1*time.Second || timeDiff > 1*time.Second {
361 t.Errorf("timestamp mismatch: got %v, want %v (diff: %v)", deserialized.AddedAt, original.AddedAt, timeDiff)
362 }
363 }
364
365 // TestNIP43Membership_ConcurrentAccess tests concurrent access to membership
366 func TestNIP43Membership_ConcurrentAccess(t *testing.T) {
367 db, cleanup := setupNIP43TestDB(t)
368 defer cleanup()
369
370 const goroutines = 10
371 const membersPerGoroutine = 5
372
373 done := make(chan bool, goroutines)
374
375 // Add members concurrently
376 for g := 0; g < goroutines; g++ {
377 go func(offset int) {
378 for i := 0; i < membersPerGoroutine; i++ {
379 pubkey := make([]byte, 32)
380 for j := range pubkey {
381 pubkey[j] = byte((offset*membersPerGoroutine+i)*10 + j)
382 }
383 if err := db.AddNIP43Member(pubkey, "code"); err != nil {
384 t.Errorf("failed to add member: %v", err)
385 }
386 }
387 done <- true
388 }(g)
389 }
390
391 // Wait for all goroutines
392 for i := 0; i < goroutines; i++ {
393 <-done
394 }
395
396 // Verify all members were added
397 members, err := db.GetAllNIP43Members()
398 if err != nil {
399 t.Fatalf("failed to get all members: %v", err)
400 }
401
402 expected := goroutines * membersPerGoroutine
403 if len(members) != expected {
404 t.Errorf("wrong member count: got %d, want %d", len(members), expected)
405 }
406 }
407