nip43_test.go raw

   1  //go:build integration
   2  // +build integration
   3  
   4  package neo4j
   5  
   6  import (
   7  	"testing"
   8  	"time"
   9  
  10  	"next.orly.dev/pkg/nostr/encoders/hex"
  11  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  12  )
  13  
  14  // All tests in this file use the shared testDB instance from testmain_test.go
  15  // to avoid Neo4j authentication rate limiting from too many connections.
  16  
  17  func TestNIP43_AddAndRemoveMember(t *testing.T) {
  18  	if testDB == nil {
  19  		t.Skip("Neo4j not available")
  20  	}
  21  
  22  	cleanTestDatabase()
  23  
  24  	signer, _ := p8k.New()
  25  	signer.Generate()
  26  	pubkey := signer.Pub()
  27  
  28  	// Add member
  29  	inviteCode := "test-invite-123"
  30  	if err := testDB.AddNIP43Member(pubkey, inviteCode); err != nil {
  31  		t.Fatalf("Failed to add NIP-43 member: %v", err)
  32  	}
  33  
  34  	// Check membership
  35  	isMember, err := testDB.IsNIP43Member(pubkey)
  36  	if err != nil {
  37  		t.Fatalf("Failed to check membership: %v", err)
  38  	}
  39  	if !isMember {
  40  		t.Fatal("Expected pubkey to be a member")
  41  	}
  42  
  43  	// Get membership details
  44  	membership, err := testDB.GetNIP43Membership(pubkey)
  45  	if err != nil {
  46  		t.Fatalf("Failed to get membership: %v", err)
  47  	}
  48  	if membership.InviteCode != inviteCode {
  49  		t.Fatalf("Invite code mismatch: got %s, expected %s", membership.InviteCode, inviteCode)
  50  	}
  51  
  52  	// Remove member
  53  	if err := testDB.RemoveNIP43Member(pubkey); err != nil {
  54  		t.Fatalf("Failed to remove member: %v", err)
  55  	}
  56  
  57  	// Verify no longer a member
  58  	isMember, _ = testDB.IsNIP43Member(pubkey)
  59  	if isMember {
  60  		t.Fatal("Expected pubkey to not be a member after removal")
  61  	}
  62  
  63  	t.Logf("✓ NIP-43 add and remove member works correctly")
  64  }
  65  
  66  func TestNIP43_GetAllMembers(t *testing.T) {
  67  	if testDB == nil {
  68  		t.Skip("Neo4j not available")
  69  	}
  70  
  71  	cleanTestDatabase()
  72  
  73  	// Add multiple members
  74  	var pubkeys [][]byte
  75  	for i := 0; i < 3; i++ {
  76  		signer, _ := p8k.New()
  77  		signer.Generate()
  78  		pubkey := signer.Pub()
  79  		pubkeys = append(pubkeys, pubkey)
  80  
  81  		if err := testDB.AddNIP43Member(pubkey, "invite"+string(rune('A'+i))); err != nil {
  82  			t.Fatalf("Failed to add member %d: %v", i, err)
  83  		}
  84  	}
  85  
  86  	// Get all members
  87  	members, err := testDB.GetAllNIP43Members()
  88  	if err != nil {
  89  		t.Fatalf("Failed to get all members: %v", err)
  90  	}
  91  
  92  	if len(members) != 3 {
  93  		t.Fatalf("Expected 3 members, got %d", len(members))
  94  	}
  95  
  96  	// Verify all added pubkeys are in the members list
  97  	memberMap := make(map[string]bool)
  98  	for _, m := range members {
  99  		memberMap[hex.Enc(m)] = true
 100  	}
 101  
 102  	for i, pk := range pubkeys {
 103  		if !memberMap[hex.Enc(pk)] {
 104  			t.Fatalf("Member %d not found in list", i)
 105  		}
 106  	}
 107  
 108  	t.Logf("✓ GetAllNIP43Members returned %d members", len(members))
 109  }
 110  
 111  func TestNIP43_InviteCode(t *testing.T) {
 112  	if testDB == nil {
 113  		t.Skip("Neo4j not available")
 114  	}
 115  
 116  	cleanTestDatabase()
 117  
 118  	// Store valid invite code (expires in 1 hour)
 119  	validCode := "valid-code-123"
 120  	expiresAt := time.Now().Add(1 * time.Hour)
 121  	if err := testDB.StoreInviteCode(validCode, expiresAt); err != nil {
 122  		t.Fatalf("Failed to store invite code: %v", err)
 123  	}
 124  
 125  	// Validate the code
 126  	isValid, err := testDB.ValidateInviteCode(validCode)
 127  	if err != nil {
 128  		t.Fatalf("Failed to validate invite code: %v", err)
 129  	}
 130  	if !isValid {
 131  		t.Fatal("Expected valid invite code to be valid")
 132  	}
 133  
 134  	// Test non-existent code
 135  	isValid, err = testDB.ValidateInviteCode("non-existent-code")
 136  	if err != nil {
 137  		t.Fatalf("Failed to validate non-existent code: %v", err)
 138  	}
 139  	if isValid {
 140  		t.Fatal("Expected non-existent code to be invalid")
 141  	}
 142  
 143  	// Delete the invite code
 144  	if err := testDB.DeleteInviteCode(validCode); err != nil {
 145  		t.Fatalf("Failed to delete invite code: %v", err)
 146  	}
 147  
 148  	// Verify code is no longer valid
 149  	isValid, _ = testDB.ValidateInviteCode(validCode)
 150  	if isValid {
 151  		t.Fatal("Expected deleted code to be invalid")
 152  	}
 153  
 154  	t.Logf("✓ NIP-43 invite code operations work correctly")
 155  }
 156  
 157  func TestNIP43_ExpiredInviteCode(t *testing.T) {
 158  	if testDB == nil {
 159  		t.Skip("Neo4j not available")
 160  	}
 161  
 162  	cleanTestDatabase()
 163  
 164  	// Store expired invite code (expired 1 hour ago)
 165  	expiredCode := "expired-code-123"
 166  	expiresAt := time.Now().Add(-1 * time.Hour)
 167  	if err := testDB.StoreInviteCode(expiredCode, expiresAt); err != nil {
 168  		t.Fatalf("Failed to store expired invite code: %v", err)
 169  	}
 170  
 171  	// Validate should return false for expired code
 172  	isValid, err := testDB.ValidateInviteCode(expiredCode)
 173  	if err != nil {
 174  		t.Fatalf("Failed to validate expired code: %v", err)
 175  	}
 176  	if isValid {
 177  		t.Fatal("Expected expired code to be invalid")
 178  	}
 179  
 180  	t.Logf("✓ Expired invite code correctly detected as invalid")
 181  }
 182  
 183  func TestNIP43_DuplicateMember(t *testing.T) {
 184  	if testDB == nil {
 185  		t.Skip("Neo4j not available")
 186  	}
 187  
 188  	cleanTestDatabase()
 189  
 190  	signer, _ := p8k.New()
 191  	signer.Generate()
 192  	pubkey := signer.Pub()
 193  
 194  	// Add member first time
 195  	if err := testDB.AddNIP43Member(pubkey, "invite1"); err != nil {
 196  		t.Fatalf("Failed to add member: %v", err)
 197  	}
 198  
 199  	// Add same member again (should not error, just update)
 200  	if err := testDB.AddNIP43Member(pubkey, "invite2"); err != nil {
 201  		t.Fatalf("Failed to re-add member: %v", err)
 202  	}
 203  
 204  	// Check membership still exists
 205  	isMember, _ := testDB.IsNIP43Member(pubkey)
 206  	if !isMember {
 207  		t.Fatal("Expected pubkey to still be a member")
 208  	}
 209  
 210  	// Get all members should have only 1 entry
 211  	members, _ := testDB.GetAllNIP43Members()
 212  	if len(members) != 1 {
 213  		t.Fatalf("Expected 1 member, got %d", len(members))
 214  	}
 215  
 216  	t.Logf("✓ Duplicate member handling works correctly")
 217  }
 218  
 219  func TestNIP43_MembershipPersistence(t *testing.T) {
 220  	if testDB == nil {
 221  		t.Skip("Neo4j not available")
 222  	}
 223  
 224  	cleanTestDatabase()
 225  
 226  	signer, _ := p8k.New()
 227  	signer.Generate()
 228  	pubkey := signer.Pub()
 229  
 230  	// Add member
 231  	inviteCode := "persistence-test"
 232  	if err := testDB.AddNIP43Member(pubkey, inviteCode); err != nil {
 233  		t.Fatalf("Failed to add member: %v", err)
 234  	}
 235  
 236  	// Get membership and verify all fields
 237  	membership, err := testDB.GetNIP43Membership(pubkey)
 238  	if err != nil {
 239  		t.Fatalf("Failed to get membership: %v", err)
 240  	}
 241  
 242  	if membership.InviteCode != inviteCode {
 243  		t.Fatalf("InviteCode mismatch")
 244  	}
 245  
 246  	if membership.AddedAt.IsZero() {
 247  		t.Fatal("AddedAt should not be zero")
 248  	}
 249  
 250  	// Verify the pubkey in membership matches
 251  	if hex.Enc(membership.Pubkey[:]) != hex.Enc(pubkey) {
 252  		t.Fatal("Pubkey mismatch in membership")
 253  	}
 254  
 255  	t.Logf("✓ NIP-43 membership persistence verified")
 256  }
 257