implemented and tested NIP-43 invite based ACL
This commit is contained in:
406
pkg/database/nip43_test.go
Normal file
406
pkg/database/nip43_test.go
Normal file
@@ -0,0 +1,406 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func setupNIP43TestDB(t *testing.T) (*D, func()) {
|
||||
tempDir, err := os.MkdirTemp("", "nip43_test_*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
db, err := New(ctx, cancel, tempDir, "info")
|
||||
if err != nil {
|
||||
os.RemoveAll(tempDir)
|
||||
t.Fatalf("failed to open database: %v", err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
db.Close()
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
return db, cleanup
|
||||
}
|
||||
|
||||
// TestAddNIP43Member tests adding a member
|
||||
func TestAddNIP43Member(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
pubkey := make([]byte, 32)
|
||||
for i := range pubkey {
|
||||
pubkey[i] = byte(i)
|
||||
}
|
||||
inviteCode := "test-invite-123"
|
||||
|
||||
err := db.AddNIP43Member(pubkey, inviteCode)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
|
||||
// Verify member was added
|
||||
isMember, err := db.IsNIP43Member(pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check membership: %v", err)
|
||||
}
|
||||
if !isMember {
|
||||
t.Error("member was not added")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddNIP43Member_InvalidPubkey tests adding member with invalid pubkey
|
||||
func TestAddNIP43Member_InvalidPubkey(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// Test with wrong length
|
||||
invalidPubkey := make([]byte, 16)
|
||||
err := db.AddNIP43Member(invalidPubkey, "test-code")
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid pubkey length")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveNIP43Member tests removing a member
|
||||
func TestRemoveNIP43Member(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
pubkey := make([]byte, 32)
|
||||
for i := range pubkey {
|
||||
pubkey[i] = byte(i)
|
||||
}
|
||||
|
||||
// Add member
|
||||
err := db.AddNIP43Member(pubkey, "test-code")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
|
||||
// Remove member
|
||||
err = db.RemoveNIP43Member(pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to remove member: %v", err)
|
||||
}
|
||||
|
||||
// Verify member was removed
|
||||
isMember, err := db.IsNIP43Member(pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check membership: %v", err)
|
||||
}
|
||||
if isMember {
|
||||
t.Error("member was not removed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsNIP43Member tests membership checking
|
||||
func TestIsNIP43Member(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
pubkey := make([]byte, 32)
|
||||
for i := range pubkey {
|
||||
pubkey[i] = byte(i)
|
||||
}
|
||||
|
||||
// Check non-existent member
|
||||
isMember, err := db.IsNIP43Member(pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check membership: %v", err)
|
||||
}
|
||||
if isMember {
|
||||
t.Error("non-existent member reported as member")
|
||||
}
|
||||
|
||||
// Add member
|
||||
err = db.AddNIP43Member(pubkey, "test-code")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
|
||||
// Check existing member
|
||||
isMember, err = db.IsNIP43Member(pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check membership: %v", err)
|
||||
}
|
||||
if !isMember {
|
||||
t.Error("existing member not found")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetNIP43Membership tests retrieving membership details
|
||||
func TestGetNIP43Membership(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
pubkey := make([]byte, 32)
|
||||
for i := range pubkey {
|
||||
pubkey[i] = byte(i)
|
||||
}
|
||||
inviteCode := "test-invite-abc123"
|
||||
|
||||
// Add member
|
||||
beforeAdd := time.Now()
|
||||
err := db.AddNIP43Member(pubkey, inviteCode)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
afterAdd := time.Now()
|
||||
|
||||
// Get membership
|
||||
membership, err := db.GetNIP43Membership(pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get membership: %v", err)
|
||||
}
|
||||
|
||||
// Verify details
|
||||
if len(membership.Pubkey) != 32 {
|
||||
t.Errorf("wrong pubkey length: got %d, want 32", len(membership.Pubkey))
|
||||
}
|
||||
for i := range pubkey {
|
||||
if membership.Pubkey[i] != pubkey[i] {
|
||||
t.Errorf("pubkey mismatch at index %d", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if membership.InviteCode != inviteCode {
|
||||
t.Errorf("invite code mismatch: got %s, want %s", membership.InviteCode, inviteCode)
|
||||
}
|
||||
|
||||
// Allow some tolerance for timestamp (database operations may take time)
|
||||
if membership.AddedAt.Before(beforeAdd.Add(-5*time.Second)) || membership.AddedAt.After(afterAdd.Add(5*time.Second)) {
|
||||
t.Errorf("AddedAt timestamp out of expected range: got %v, expected between %v and %v",
|
||||
membership.AddedAt, beforeAdd, afterAdd)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetAllNIP43Members tests retrieving all members
|
||||
func TestGetAllNIP43Members(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// Add multiple members
|
||||
memberCount := 5
|
||||
for i := 0; i < memberCount; i++ {
|
||||
pubkey := make([]byte, 32)
|
||||
for j := range pubkey {
|
||||
pubkey[j] = byte(i*10 + j)
|
||||
}
|
||||
err := db.AddNIP43Member(pubkey, "code-"+string(rune(i)))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add member %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get all members
|
||||
members, err := db.GetAllNIP43Members()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get all members: %v", err)
|
||||
}
|
||||
|
||||
if len(members) != memberCount {
|
||||
t.Errorf("wrong member count: got %d, want %d", len(members), memberCount)
|
||||
}
|
||||
|
||||
// Verify each member has valid pubkey
|
||||
for i, member := range members {
|
||||
if len(member) != 32 {
|
||||
t.Errorf("member %d has invalid pubkey length: %d", i, len(member))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestStoreInviteCode tests storing invite codes
|
||||
func TestStoreInviteCode(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
code := "test-invite-xyz789"
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
|
||||
err := db.StoreInviteCode(code, expiresAt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to store invite code: %v", err)
|
||||
}
|
||||
|
||||
// Validate the code
|
||||
valid, err := db.ValidateInviteCode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to validate invite code: %v", err)
|
||||
}
|
||||
if !valid {
|
||||
t.Error("stored invite code is not valid")
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateInviteCode_Expired tests expired invite code handling
|
||||
func TestValidateInviteCode_Expired(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
code := "expired-code"
|
||||
expiresAt := time.Now().Add(-1 * time.Hour) // Already expired
|
||||
|
||||
err := db.StoreInviteCode(code, expiresAt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to store invite code: %v", err)
|
||||
}
|
||||
|
||||
// Validate the code - should be invalid because it's expired
|
||||
valid, err := db.ValidateInviteCode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to validate invite code: %v", err)
|
||||
}
|
||||
if valid {
|
||||
t.Error("expired invite code reported as valid")
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateInviteCode_NonExistent tests non-existent code validation
|
||||
func TestValidateInviteCode_NonExistent(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
valid, err := db.ValidateInviteCode("non-existent-code")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if valid {
|
||||
t.Error("non-existent code reported as valid")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteInviteCode tests deleting invite codes
|
||||
func TestDeleteInviteCode(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
code := "delete-me-code"
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
|
||||
// Store code
|
||||
err := db.StoreInviteCode(code, expiresAt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to store invite code: %v", err)
|
||||
}
|
||||
|
||||
// Verify it exists
|
||||
valid, err := db.ValidateInviteCode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to validate invite code: %v", err)
|
||||
}
|
||||
if !valid {
|
||||
t.Error("stored code is not valid")
|
||||
}
|
||||
|
||||
// Delete code
|
||||
err = db.DeleteInviteCode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to delete invite code: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's gone
|
||||
valid, err = db.ValidateInviteCode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to validate after delete: %v", err)
|
||||
}
|
||||
if valid {
|
||||
t.Error("deleted code still valid")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNIP43Membership_Serialization tests membership serialization
|
||||
func TestNIP43Membership_Serialization(t *testing.T) {
|
||||
pubkey := make([]byte, 32)
|
||||
for i := range pubkey {
|
||||
pubkey[i] = byte(i)
|
||||
}
|
||||
|
||||
original := NIP43Membership{
|
||||
Pubkey: pubkey,
|
||||
AddedAt: time.Now(),
|
||||
InviteCode: "test-code-123",
|
||||
}
|
||||
|
||||
// Serialize
|
||||
data := serializeNIP43Membership(original)
|
||||
|
||||
// Deserialize
|
||||
deserialized := deserializeNIP43Membership(data)
|
||||
|
||||
// Verify
|
||||
if deserialized == nil {
|
||||
t.Fatal("deserialization returned nil")
|
||||
}
|
||||
|
||||
if len(deserialized.Pubkey) != 32 {
|
||||
t.Errorf("wrong pubkey length: got %d, want 32", len(deserialized.Pubkey))
|
||||
}
|
||||
|
||||
for i := range pubkey {
|
||||
if deserialized.Pubkey[i] != pubkey[i] {
|
||||
t.Errorf("pubkey mismatch at index %d", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if deserialized.InviteCode != original.InviteCode {
|
||||
t.Errorf("invite code mismatch: got %s, want %s", deserialized.InviteCode, original.InviteCode)
|
||||
}
|
||||
|
||||
// Allow 1 second tolerance for timestamp comparison (due to Unix conversion)
|
||||
timeDiff := deserialized.AddedAt.Sub(original.AddedAt)
|
||||
if timeDiff < -1*time.Second || timeDiff > 1*time.Second {
|
||||
t.Errorf("timestamp mismatch: got %v, want %v (diff: %v)", deserialized.AddedAt, original.AddedAt, timeDiff)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNIP43Membership_ConcurrentAccess tests concurrent access to membership
|
||||
func TestNIP43Membership_ConcurrentAccess(t *testing.T) {
|
||||
db, cleanup := setupNIP43TestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
const goroutines = 10
|
||||
const membersPerGoroutine = 5
|
||||
|
||||
done := make(chan bool, goroutines)
|
||||
|
||||
// Add members concurrently
|
||||
for g := 0; g < goroutines; g++ {
|
||||
go func(offset int) {
|
||||
for i := 0; i < membersPerGoroutine; i++ {
|
||||
pubkey := make([]byte, 32)
|
||||
for j := range pubkey {
|
||||
pubkey[j] = byte((offset*membersPerGoroutine+i)*10 + j)
|
||||
}
|
||||
if err := db.AddNIP43Member(pubkey, "code"); err != nil {
|
||||
t.Errorf("failed to add member: %v", err)
|
||||
}
|
||||
}
|
||||
done <- true
|
||||
}(g)
|
||||
}
|
||||
|
||||
// Wait for all goroutines
|
||||
for i := 0; i < goroutines; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
// Verify all members were added
|
||||
members, err := db.GetAllNIP43Members()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get all members: %v", err)
|
||||
}
|
||||
|
||||
expected := goroutines * membersPerGoroutine
|
||||
if len(members) != expected {
|
||||
t.Errorf("wrong member count: got %d, want %d", len(members), expected)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user