handle-nip43_test.go raw

   1  package app
   2  
   3  import (
   4  	"context"
   5  	"os"
   6  	"testing"
   7  	"time"
   8  
   9  	"next.orly.dev/app/config"
  10  	"next.orly.dev/pkg/acl"
  11  	"next.orly.dev/pkg/nostr/crypto/keys"
  12  	"next.orly.dev/pkg/database"
  13  	"next.orly.dev/pkg/nostr/encoders/event"
  14  	"next.orly.dev/pkg/nostr/encoders/hex"
  15  	"next.orly.dev/pkg/nostr/encoders/tag"
  16  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  17  	"next.orly.dev/pkg/protocol/nip43"
  18  	"next.orly.dev/pkg/protocol/publish"
  19  )
  20  
  21  // setupTestListener creates a test listener with NIP-43 enabled
  22  func setupTestListener(t *testing.T) (*Listener, *database.D, func()) {
  23  	tempDir, err := os.MkdirTemp("", "nip43_handler_test_*")
  24  	if err != nil {
  25  		t.Fatalf("failed to create temp dir: %v", err)
  26  	}
  27  
  28  	ctx, cancel := context.WithCancel(context.Background())
  29  	db, err := database.New(ctx, cancel, tempDir, "info")
  30  	if err != nil {
  31  		os.RemoveAll(tempDir)
  32  		t.Fatalf("failed to open database: %v", err)
  33  	}
  34  
  35  	cfg := &config.C{
  36  		NIP43Enabled:           true,
  37  		NIP43PublishEvents:     true,
  38  		NIP43PublishMemberList: true,
  39  		NIP43InviteExpiry:      24 * time.Hour,
  40  		RelayURL:               "wss://test.relay",
  41  		Listen:                 "localhost",
  42  		Port:                   3334,
  43  		ACLMode:                "none",
  44  	}
  45  
  46  	server := &Server{
  47  		Ctx:           ctx,
  48  		Config:        cfg,
  49  		DB:            db,
  50  		publishers:    publish.New(NewPublisher(ctx)),
  51  		InviteManager: nip43.NewInviteManager(cfg.NIP43InviteExpiry),
  52  		cfg:           cfg,
  53  		db:            db,
  54  	}
  55  
  56  	// Configure ACL registry
  57  	acl.Registry.SetMode(cfg.ACLMode)
  58  	if err = acl.Registry.Configure(cfg, db, ctx); err != nil {
  59  		db.Close()
  60  		os.RemoveAll(tempDir)
  61  		t.Fatalf("failed to configure ACL: %v", err)
  62  	}
  63  
  64  	listener := &Listener{
  65  		Server:         server,
  66  		ctx:            ctx,
  67  		writeChan:      make(chan publish.WriteRequest, 100),
  68  		writeDone:      make(chan struct{}),
  69  		messageQueue:   make(chan messageRequest, 100),
  70  		processingDone: make(chan struct{}),
  71  		subscriptions:  make(map[string]context.CancelFunc),
  72  	}
  73  
  74  	// Start write worker and message processor
  75  	go listener.writeWorker()
  76  	go listener.messageProcessor()
  77  
  78  	cleanup := func() {
  79  		// Close listener channels
  80  		close(listener.writeChan)
  81  		<-listener.writeDone
  82  		close(listener.messageQueue)
  83  		<-listener.processingDone
  84  		db.Close()
  85  		os.RemoveAll(tempDir)
  86  	}
  87  
  88  	return listener, db, cleanup
  89  }
  90  
  91  // TestHandleNIP43JoinRequest_ValidRequest tests a successful join request
  92  func TestHandleNIP43JoinRequest_ValidRequest(t *testing.T) {
  93  	listener, db, cleanup := setupTestListener(t)
  94  	defer cleanup()
  95  
  96  	// Generate test user
  97  	userSecret, err := keys.GenerateSecretKey()
  98  	if err != nil {
  99  		t.Fatalf("failed to generate user secret: %v", err)
 100  	}
 101  	userSigner, err := p8k.New()
 102  	if err != nil {
 103  		t.Fatalf("failed to create signer: %v", err)
 104  	}
 105  	if err = userSigner.InitSec(userSecret); err != nil {
 106  		t.Fatalf("failed to initialize signer: %v", err)
 107  	}
 108  	userPubkey := userSigner.Pub()
 109  
 110  	// Generate invite code
 111  	code, err := listener.Server.InviteManager.GenerateCode()
 112  	if err != nil {
 113  		t.Fatalf("failed to generate invite code: %v", err)
 114  	}
 115  
 116  	// Create join request event
 117  	ev := event.New()
 118  	ev.Kind = nip43.KindJoinRequest
 119  	copy(ev.Pubkey, userPubkey)
 120  	ev.Tags = tag.NewS()
 121  	ev.Tags.Append(tag.NewFromAny("-"))
 122  	ev.Tags.Append(tag.NewFromAny("claim", code))
 123  	ev.CreatedAt = time.Now().Unix()
 124  	ev.Content = []byte("")
 125  
 126  	// Sign event
 127  	if err = ev.Sign(userSigner); err != nil {
 128  		t.Fatalf("failed to sign event: %v", err)
 129  	}
 130  
 131  	// Handle join request
 132  	err = listener.HandleNIP43JoinRequest(ev)
 133  	if err != nil {
 134  		t.Fatalf("failed to handle join request: %v", err)
 135  	}
 136  
 137  	// Verify user was added to database
 138  	isMember, err := db.IsNIP43Member(userPubkey)
 139  	if err != nil {
 140  		t.Fatalf("failed to check membership: %v", err)
 141  	}
 142  	if !isMember {
 143  		t.Error("user was not added as member")
 144  	}
 145  
 146  	// Verify membership details
 147  	membership, err := db.GetNIP43Membership(userPubkey)
 148  	if err != nil {
 149  		t.Fatalf("failed to get membership: %v", err)
 150  	}
 151  	if membership.InviteCode != code {
 152  		t.Errorf("wrong invite code stored: got %s, want %s", membership.InviteCode, code)
 153  	}
 154  }
 155  
 156  // TestHandleNIP43JoinRequest_InvalidCode tests join request with invalid code
 157  func TestHandleNIP43JoinRequest_InvalidCode(t *testing.T) {
 158  	listener, db, cleanup := setupTestListener(t)
 159  	defer cleanup()
 160  
 161  	// Generate test user
 162  	userSecret, err := keys.GenerateSecretKey()
 163  	if err != nil {
 164  		t.Fatalf("failed to generate user secret: %v", err)
 165  	}
 166  	userSigner, err := p8k.New()
 167  	if err != nil {
 168  		t.Fatalf("failed to create signer: %v", err)
 169  	}
 170  	if err = userSigner.InitSec(userSecret); err != nil {
 171  		t.Fatalf("failed to initialize signer: %v", err)
 172  	}
 173  	userPubkey := userSigner.Pub()
 174  
 175  	// Create join request with invalid code
 176  	ev := event.New()
 177  	ev.Kind = nip43.KindJoinRequest
 178  	copy(ev.Pubkey, userPubkey)
 179  	ev.Tags = tag.NewS()
 180  	ev.Tags.Append(tag.NewFromAny("-"))
 181  	ev.Tags.Append(tag.NewFromAny("claim", "invalid-code-123"))
 182  	ev.CreatedAt = time.Now().Unix()
 183  	ev.Content = []byte("")
 184  
 185  	if err = ev.Sign(userSigner); err != nil {
 186  		t.Fatalf("failed to sign event: %v", err)
 187  	}
 188  
 189  	// Handle join request - should succeed but not add member
 190  	err = listener.HandleNIP43JoinRequest(ev)
 191  	if err != nil {
 192  		t.Fatalf("handler returned error: %v", err)
 193  	}
 194  
 195  	// Verify user was NOT added
 196  	isMember, err := db.IsNIP43Member(userPubkey)
 197  	if err != nil {
 198  		t.Fatalf("failed to check membership: %v", err)
 199  	}
 200  	if isMember {
 201  		t.Error("user was incorrectly added as member with invalid code")
 202  	}
 203  }
 204  
 205  // TestHandleNIP43JoinRequest_DuplicateMember tests join request from existing member
 206  func TestHandleNIP43JoinRequest_DuplicateMember(t *testing.T) {
 207  	listener, db, cleanup := setupTestListener(t)
 208  	defer cleanup()
 209  
 210  	// Generate test user
 211  	userSecret, err := keys.GenerateSecretKey()
 212  	if err != nil {
 213  		t.Fatalf("failed to generate user secret: %v", err)
 214  	}
 215  	userSigner, err := p8k.New()
 216  	if err != nil {
 217  		t.Fatalf("failed to create signer: %v", err)
 218  	}
 219  	if err = userSigner.InitSec(userSecret); err != nil {
 220  		t.Fatalf("failed to initialize signer: %v", err)
 221  	}
 222  	userPubkey := userSigner.Pub()
 223  
 224  	// Add user directly to database
 225  	err = db.AddNIP43Member(userPubkey, "original-code")
 226  	if err != nil {
 227  		t.Fatalf("failed to add member: %v", err)
 228  	}
 229  
 230  	// Generate new invite code
 231  	code, err := listener.Server.InviteManager.GenerateCode()
 232  	if err != nil {
 233  		t.Fatalf("failed to generate invite code: %v", err)
 234  	}
 235  
 236  	// Create join request
 237  	ev := event.New()
 238  	ev.Kind = nip43.KindJoinRequest
 239  	copy(ev.Pubkey, userPubkey)
 240  	ev.Tags = tag.NewS()
 241  	ev.Tags.Append(tag.NewFromAny("-"))
 242  	ev.Tags.Append(tag.NewFromAny("claim", code))
 243  	ev.CreatedAt = time.Now().Unix()
 244  	ev.Content = []byte("")
 245  
 246  	if err = ev.Sign(userSigner); err != nil {
 247  		t.Fatalf("failed to sign event: %v", err)
 248  	}
 249  
 250  	// Handle join request - should handle gracefully
 251  	err = listener.HandleNIP43JoinRequest(ev)
 252  	if err != nil {
 253  		t.Fatalf("handler returned error: %v", err)
 254  	}
 255  
 256  	// Verify original membership is unchanged
 257  	membership, err := db.GetNIP43Membership(userPubkey)
 258  	if err != nil {
 259  		t.Fatalf("failed to get membership: %v", err)
 260  	}
 261  	if membership.InviteCode != "original-code" {
 262  		t.Errorf("invite code was changed: got %s, want original-code", membership.InviteCode)
 263  	}
 264  }
 265  
 266  // TestHandleNIP43LeaveRequest_ValidRequest tests a successful leave request
 267  func TestHandleNIP43LeaveRequest_ValidRequest(t *testing.T) {
 268  	listener, db, cleanup := setupTestListener(t)
 269  	defer cleanup()
 270  
 271  	// Generate test user
 272  	userSecret, err := keys.GenerateSecretKey()
 273  	if err != nil {
 274  		t.Fatalf("failed to generate user secret: %v", err)
 275  	}
 276  	userSigner, err := p8k.New()
 277  	if err != nil {
 278  		t.Fatalf("failed to create signer: %v", err)
 279  	}
 280  	if err = userSigner.InitSec(userSecret); err != nil {
 281  		t.Fatalf("failed to initialize signer: %v", err)
 282  	}
 283  	userPubkey := userSigner.Pub()
 284  
 285  	// Add user as member
 286  	err = db.AddNIP43Member(userPubkey, "test-code")
 287  	if err != nil {
 288  		t.Fatalf("failed to add member: %v", err)
 289  	}
 290  
 291  	// Create leave request
 292  	ev := event.New()
 293  	ev.Kind = nip43.KindLeaveRequest
 294  	copy(ev.Pubkey, userPubkey)
 295  	ev.Tags = tag.NewS()
 296  	ev.Tags.Append(tag.NewFromAny("-"))
 297  	ev.CreatedAt = time.Now().Unix()
 298  	ev.Content = []byte("")
 299  
 300  	if err = ev.Sign(userSigner); err != nil {
 301  		t.Fatalf("failed to sign event: %v", err)
 302  	}
 303  
 304  	// Handle leave request
 305  	err = listener.HandleNIP43LeaveRequest(ev)
 306  	if err != nil {
 307  		t.Fatalf("failed to handle leave request: %v", err)
 308  	}
 309  
 310  	// Verify user was removed
 311  	isMember, err := db.IsNIP43Member(userPubkey)
 312  	if err != nil {
 313  		t.Fatalf("failed to check membership: %v", err)
 314  	}
 315  	if isMember {
 316  		t.Error("user was not removed")
 317  	}
 318  }
 319  
 320  // TestHandleNIP43LeaveRequest_NonMember tests leave request from non-member
 321  func TestHandleNIP43LeaveRequest_NonMember(t *testing.T) {
 322  	listener, _, cleanup := setupTestListener(t)
 323  	defer cleanup()
 324  
 325  	// Generate test user (not a member)
 326  	userSecret, err := keys.GenerateSecretKey()
 327  	if err != nil {
 328  		t.Fatalf("failed to generate user secret: %v", err)
 329  	}
 330  	userSigner, err := p8k.New()
 331  	if err != nil {
 332  		t.Fatalf("failed to create signer: %v", err)
 333  	}
 334  	if err = userSigner.InitSec(userSecret); err != nil {
 335  		t.Fatalf("failed to initialize signer: %v", err)
 336  	}
 337  	userPubkey := userSigner.Pub()
 338  
 339  	// Create leave request
 340  	ev := event.New()
 341  	ev.Kind = nip43.KindLeaveRequest
 342  	copy(ev.Pubkey, userPubkey)
 343  	ev.Tags = tag.NewS()
 344  	ev.Tags.Append(tag.NewFromAny("-"))
 345  	ev.CreatedAt = time.Now().Unix()
 346  	ev.Content = []byte("")
 347  
 348  	if err = ev.Sign(userSigner); err != nil {
 349  		t.Fatalf("failed to sign event: %v", err)
 350  	}
 351  
 352  	// Handle leave request - should handle gracefully
 353  	err = listener.HandleNIP43LeaveRequest(ev)
 354  	if err != nil {
 355  		t.Fatalf("handler returned error: %v", err)
 356  	}
 357  }
 358  
 359  // TestHandleNIP43InviteRequest_ValidRequest tests invite request from admin
 360  func TestHandleNIP43InviteRequest_ValidRequest(t *testing.T) {
 361  	listener, _, cleanup := setupTestListener(t)
 362  	defer cleanup()
 363  
 364  	// Generate admin user
 365  	adminSecret, err := keys.GenerateSecretKey()
 366  	if err != nil {
 367  		t.Fatalf("failed to generate admin secret: %v", err)
 368  	}
 369  	adminSigner, err := p8k.New()
 370  	if err != nil {
 371  		t.Fatalf("failed to create signer: %v", err)
 372  	}
 373  	if err = adminSigner.InitSec(adminSecret); err != nil {
 374  		t.Fatalf("failed to initialize signer: %v", err)
 375  	}
 376  	adminPubkey := adminSigner.Pub()
 377  
 378  	// Add admin to config and reconfigure ACL
 379  	adminHex := hex.Enc(adminPubkey)
 380  	listener.Server.Config.Admins = []string{adminHex}
 381  	acl.Registry.SetMode("none")
 382  	if err = acl.Registry.Configure(listener.Server.Config, listener.Server.DB, listener.ctx); err != nil {
 383  		t.Fatalf("failed to reconfigure ACL: %v", err)
 384  	}
 385  
 386  	// Handle invite request
 387  	inviteEvent, err := listener.Server.HandleNIP43InviteRequest(adminPubkey)
 388  	if err != nil {
 389  		t.Fatalf("failed to handle invite request: %v", err)
 390  	}
 391  
 392  	// Verify invite event
 393  	if inviteEvent == nil {
 394  		t.Fatal("invite event is nil")
 395  	}
 396  	if inviteEvent.Kind != nip43.KindInviteReq {
 397  		t.Errorf("wrong event kind: got %d, want %d", inviteEvent.Kind, nip43.KindInviteReq)
 398  	}
 399  
 400  	// Verify claim tag
 401  	claimTag := inviteEvent.Tags.GetFirst([]byte("claim"))
 402  	if claimTag == nil {
 403  		t.Fatal("missing claim tag")
 404  	}
 405  	if claimTag.Len() < 2 {
 406  		t.Fatal("claim tag has no value")
 407  	}
 408  }
 409  
 410  // TestHandleNIP43InviteRequest_Unauthorized tests invite request from non-admin
 411  func TestHandleNIP43InviteRequest_Unauthorized(t *testing.T) {
 412  	listener, _, cleanup := setupTestListener(t)
 413  	defer cleanup()
 414  
 415  	// Generate regular user (not admin)
 416  	userSecret, err := keys.GenerateSecretKey()
 417  	if err != nil {
 418  		t.Fatalf("failed to generate user secret: %v", err)
 419  	}
 420  	userSigner, err := p8k.New()
 421  	if err != nil {
 422  		t.Fatalf("failed to create signer: %v", err)
 423  	}
 424  	if err = userSigner.InitSec(userSecret); err != nil {
 425  		t.Fatalf("failed to initialize signer: %v", err)
 426  	}
 427  	userPubkey := userSigner.Pub()
 428  
 429  	// Handle invite request - should fail
 430  	_, err = listener.Server.HandleNIP43InviteRequest(userPubkey)
 431  	if err == nil {
 432  		t.Fatal("expected error for unauthorized user")
 433  	}
 434  }
 435  
 436  // TestJoinAndLeaveFlow tests the complete join and leave flow
 437  func TestJoinAndLeaveFlow(t *testing.T) {
 438  	listener, db, cleanup := setupTestListener(t)
 439  	defer cleanup()
 440  
 441  	// Generate test user
 442  	userSecret, err := keys.GenerateSecretKey()
 443  	if err != nil {
 444  		t.Fatalf("failed to generate user secret: %v", err)
 445  	}
 446  	userSigner, err := p8k.New()
 447  	if err != nil {
 448  		t.Fatalf("failed to create signer: %v", err)
 449  	}
 450  	if err = userSigner.InitSec(userSecret); err != nil {
 451  		t.Fatalf("failed to initialize signer: %v", err)
 452  	}
 453  	userPubkey := userSigner.Pub()
 454  
 455  	// Step 1: Generate invite code
 456  	code, err := listener.Server.InviteManager.GenerateCode()
 457  	if err != nil {
 458  		t.Fatalf("failed to generate invite code: %v", err)
 459  	}
 460  
 461  	// Step 2: User sends join request
 462  	joinEv := event.New()
 463  	joinEv.Kind = nip43.KindJoinRequest
 464  	copy(joinEv.Pubkey, userPubkey)
 465  	joinEv.Tags = tag.NewS()
 466  	joinEv.Tags.Append(tag.NewFromAny("-"))
 467  	joinEv.Tags.Append(tag.NewFromAny("claim", code))
 468  	joinEv.CreatedAt = time.Now().Unix()
 469  	joinEv.Content = []byte("")
 470  	if err = joinEv.Sign(userSigner); err != nil {
 471  		t.Fatalf("failed to sign join event: %v", err)
 472  	}
 473  
 474  	err = listener.HandleNIP43JoinRequest(joinEv)
 475  	if err != nil {
 476  		t.Fatalf("failed to handle join request: %v", err)
 477  	}
 478  
 479  	// Verify user is member
 480  	isMember, err := db.IsNIP43Member(userPubkey)
 481  	if err != nil {
 482  		t.Fatalf("failed to check membership after join: %v", err)
 483  	}
 484  	if !isMember {
 485  		t.Fatal("user is not a member after join")
 486  	}
 487  
 488  	// Step 3: User sends leave request
 489  	leaveEv := event.New()
 490  	leaveEv.Kind = nip43.KindLeaveRequest
 491  	copy(leaveEv.Pubkey, userPubkey)
 492  	leaveEv.Tags = tag.NewS()
 493  	leaveEv.Tags.Append(tag.NewFromAny("-"))
 494  	leaveEv.CreatedAt = time.Now().Unix()
 495  	leaveEv.Content = []byte("")
 496  	if err = leaveEv.Sign(userSigner); err != nil {
 497  		t.Fatalf("failed to sign leave event: %v", err)
 498  	}
 499  
 500  	err = listener.HandleNIP43LeaveRequest(leaveEv)
 501  	if err != nil {
 502  		t.Fatalf("failed to handle leave request: %v", err)
 503  	}
 504  
 505  	// Verify user is no longer member
 506  	isMember, err = db.IsNIP43Member(userPubkey)
 507  	if err != nil {
 508  		t.Fatalf("failed to check membership after leave: %v", err)
 509  	}
 510  	if isMember {
 511  		t.Fatal("user is still a member after leave")
 512  	}
 513  }
 514  
 515  // TestMultipleUsersJoining tests multiple users joining concurrently
 516  func TestMultipleUsersJoining(t *testing.T) {
 517  	listener, db, cleanup := setupTestListener(t)
 518  	defer cleanup()
 519  
 520  	userCount := 10
 521  	done := make(chan bool, userCount)
 522  
 523  	for i := 0; i < userCount; i++ {
 524  		go func(index int) {
 525  			// Generate user
 526  			userSecret, err := keys.GenerateSecretKey()
 527  			if err != nil {
 528  				t.Errorf("failed to generate user secret %d: %v", index, err)
 529  				done <- false
 530  				return
 531  			}
 532  			userSigner, err := p8k.New()
 533  			if err != nil {
 534  				t.Errorf("failed to create signer %d: %v", index, err)
 535  				done <- false
 536  				return
 537  			}
 538  			if err = userSigner.InitSec(userSecret); err != nil {
 539  				t.Errorf("failed to initialize signer %d: %v", index, err)
 540  				done <- false
 541  				return
 542  			}
 543  			userPubkey := userSigner.Pub()
 544  
 545  			// Generate invite code
 546  			code, err := listener.Server.InviteManager.GenerateCode()
 547  			if err != nil {
 548  				t.Errorf("failed to generate invite code %d: %v", index, err)
 549  				done <- false
 550  				return
 551  			}
 552  
 553  			// Create join request
 554  			joinEv := event.New()
 555  			joinEv.Kind = nip43.KindJoinRequest
 556  			copy(joinEv.Pubkey, userPubkey)
 557  			joinEv.Tags = tag.NewS()
 558  			joinEv.Tags.Append(tag.NewFromAny("-"))
 559  			joinEv.Tags.Append(tag.NewFromAny("claim", code))
 560  			joinEv.CreatedAt = time.Now().Unix()
 561  			joinEv.Content = []byte("")
 562  			if err = joinEv.Sign(userSigner); err != nil {
 563  				t.Errorf("failed to sign event %d: %v", index, err)
 564  				done <- false
 565  				return
 566  			}
 567  
 568  			// Handle join request
 569  			if err = listener.HandleNIP43JoinRequest(joinEv); err != nil {
 570  				t.Errorf("failed to handle join request %d: %v", index, err)
 571  				done <- false
 572  				return
 573  			}
 574  
 575  			done <- true
 576  		}(i)
 577  	}
 578  
 579  	// Wait for all goroutines
 580  	successCount := 0
 581  	for i := 0; i < userCount; i++ {
 582  		if <-done {
 583  			successCount++
 584  		}
 585  	}
 586  
 587  	if successCount != userCount {
 588  		t.Errorf("not all users joined successfully: %d/%d", successCount, userCount)
 589  	}
 590  
 591  	// Verify member count
 592  	members, err := db.GetAllNIP43Members()
 593  	if err != nil {
 594  		t.Fatalf("failed to get all members: %v", err)
 595  	}
 596  
 597  	if len(members) != successCount {
 598  		t.Errorf("wrong member count: got %d, want %d", len(members), successCount)
 599  	}
 600  }
 601