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