package database import ( "bufio" "bytes" "context" "os" "sort" "testing" "time" "git.mleku.dev/mleku/nostr/encoders/event" "git.mleku.dev/mleku/nostr/encoders/event/examples" "git.mleku.dev/mleku/nostr/encoders/hex" "git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/timestamp" "git.mleku.dev/mleku/nostr/interfaces/signer/p8k" "lol.mleku.dev/chk" "lol.mleku.dev/errorf" ) // TestSaveEvents tests saving all events from examples.Cache to the database // to verify there are no errors during the saving process. func TestSaveEvents(t *testing.T) { // Create a temporary directory for the database tempDir, err := os.MkdirTemp("", "test-db-*") if err != nil { t.Fatalf("Failed to create temporary directory: %v", err) } defer os.RemoveAll(tempDir) // Clean up after the test // Create a context and cancel function for the database ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Initialize the database db, err := New(ctx, cancel, tempDir, "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() // Create a scanner to read events from examples.Cache scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache)) scanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000) // Collect all events first var events []*event.E var original int for scanner.Scan() { chk.E(scanner.Err()) b := scanner.Bytes() original += len(b) ev := event.New() // Unmarshal the event if _, err = ev.Unmarshal(b); chk.E(err) { t.Fatal(err) } events = append(events, ev) } // Sort events by timestamp to ensure addressable events are processed in order sort.Slice(events, func(i, j int) bool { return events[i].CreatedAt < events[j].CreatedAt }) // Count the number of events processed eventCount := 0 skippedCount := 0 var kc, vc int now := time.Now() // Process each event in chronological order for _, ev := range events { // Save the event to the database var k, v int if _, err = db.SaveEvent(ctx, ev); err != nil { // Skip events that fail validation (e.g., kind 3 without p tags) skippedCount++ continue } kc += k vc += v eventCount++ } _ = skippedCount // Used for logging below // Check for scanner errors if err = scanner.Err(); err != nil { t.Fatalf("Scanner error: %v", err) } dur := time.Since(now) t.Logf( "Successfully saved %d events %d bytes to the database, %d bytes keys, %d bytes values in %v (%v/ev; %f ev/s)", eventCount, original, kc, vc, dur, dur/time.Duration(eventCount), float64(time.Second)/float64(dur/time.Duration(eventCount)), ) } // TestDeletionEventWithETagRejection tests that a deletion event with an "e" tag is rejected. func TestDeletionEventWithETagRejection(t *testing.T) { // Create a temporary directory for the database tempDir, err := os.MkdirTemp("", "test-db-*") if err != nil { t.Fatalf("Failed to create temporary directory: %v", err) } defer os.RemoveAll(tempDir) // Clean up after the test // Create a context and cancel function for the database ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Initialize the database db, err := New(ctx, cancel, tempDir, "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() // Create a signer sign := p8k.MustNew() if err := sign.Generate(); chk.E(err) { t.Fatal(err) } // Create a regular event regularEvent := event.New() regularEvent.Kind = kind.TextNote.K regularEvent.Pubkey = sign.Pub() regularEvent.CreatedAt = timestamp.Now().V - 3600 // 1 hour ago regularEvent.Content = []byte("Regular event") regularEvent.Tags = tag.NewS() regularEvent.Sign(sign) // Save the regular event if _, err := db.SaveEvent(ctx, regularEvent); err != nil { t.Fatalf("Failed to save regular event: %v", err) } // Create a deletion event with an "e" tag referencing the regular event deletionEvent := event.New() deletionEvent.Kind = kind.Deletion.K deletionEvent.Pubkey = sign.Pub() deletionEvent.CreatedAt = timestamp.Now().V // Current time deletionEvent.Content = []byte("Deleting the regular event") deletionEvent.Tags = tag.NewS() // Add an e-tag referencing the regular event *deletionEvent.Tags = append( *deletionEvent.Tags, tag.NewFromAny("e", hex.Enc(regularEvent.ID)), ) deletionEvent.Sign(sign) // Check if this is a deletion event with "e" tags if deletionEvent.Kind == kind.Deletion.K && deletionEvent.Tags.GetFirst([]byte{'e'}) != nil { // In this test, we want to reject deletion events with "e" tags err = errorf.E("deletion events referencing other events with 'e' tag are not allowed") } else { // Try to save the deletion event _, err = db.SaveEvent(ctx, deletionEvent) } if err == nil { t.Fatal("Expected deletion event with e-tag to be rejected, but it was accepted") } // Verify the error message expectedError := "deletion events referencing other events with 'e' tag are not allowed" if err.Error() != expectedError { t.Fatalf( "Expected error message '%s', got '%s'", expectedError, err.Error(), ) } } // TestSaveExistingEvent tests that attempting to save an event that already exists // returns an error. func TestSaveExistingEvent(t *testing.T) { // Create a temporary directory for the database tempDir, err := os.MkdirTemp("", "test-db-*") if err != nil { t.Fatalf("Failed to create temporary directory: %v", err) } defer os.RemoveAll(tempDir) // Clean up after the test // Create a context and cancel function for the database ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Initialize the database db, err := New(ctx, cancel, tempDir, "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() // Create a signer sign := p8k.MustNew() if err := sign.Generate(); chk.E(err) { t.Fatal(err) } // Create an event ev := event.New() ev.Kind = kind.TextNote.K ev.Pubkey = sign.Pub() ev.CreatedAt = timestamp.Now().V ev.Content = []byte("Test event") ev.Tags = tag.NewS() ev.Sign(sign) // Save the event for the first time if _, err := db.SaveEvent(ctx, ev); err != nil { t.Fatalf("Failed to save event: %v", err) } // Try to save the same event again, it should be rejected _, err = db.SaveEvent(ctx, ev) if err == nil { t.Fatal("Expected error when saving an existing event, but got nil") } // Verify the error message expectedErrorPrefix := "blocked: event already exists: " if !bytes.HasPrefix([]byte(err.Error()), []byte(expectedErrorPrefix)) { t.Fatalf( "Expected error message to start with '%s', got '%s'", expectedErrorPrefix, err.Error(), ) } }