implement wasm/js specific database engine
This commit is contained in:
555
pkg/neo4j/delete_test.go
Normal file
555
pkg/neo4j/delete_test.go
Normal file
@@ -0,0 +1,555 @@
|
||||
package neo4j
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.mleku.dev/mleku/nostr/encoders/event"
|
||||
"git.mleku.dev/mleku/nostr/encoders/filter"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestDeleteEvent(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
signer, err := p8k.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create signer: %v", err)
|
||||
}
|
||||
if err := signer.Generate(); err != nil {
|
||||
t.Fatalf("Failed to generate keypair: %v", err)
|
||||
}
|
||||
|
||||
// Create and save event
|
||||
ev := event.New()
|
||||
ev.Pubkey = signer.Pub()
|
||||
ev.CreatedAt = timestamp.Now().V
|
||||
ev.Kind = 1
|
||||
ev.Content = []byte("Event to be deleted")
|
||||
|
||||
if err := ev.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, ev); err != nil {
|
||||
t.Fatalf("Failed to save event: %v", err)
|
||||
}
|
||||
|
||||
// Verify event exists
|
||||
evs, err := db.QueryEvents(ctx, &filter.F{
|
||||
Ids: tag.NewFromBytesSlice(ev.ID),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query event: %v", err)
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
t.Fatalf("Expected 1 event before deletion, got %d", len(evs))
|
||||
}
|
||||
|
||||
// Delete the event
|
||||
if err := db.DeleteEvent(ctx, ev.ID[:]); err != nil {
|
||||
t.Fatalf("Failed to delete event: %v", err)
|
||||
}
|
||||
|
||||
// Verify event is deleted
|
||||
evs, err = db.QueryEvents(ctx, &filter.F{
|
||||
Ids: tag.NewFromBytesSlice(ev.ID),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query after deletion: %v", err)
|
||||
}
|
||||
if len(evs) != 0 {
|
||||
t.Fatalf("Expected 0 events after deletion, got %d", len(evs))
|
||||
}
|
||||
|
||||
t.Logf("✓ DeleteEvent successfully removed event")
|
||||
}
|
||||
|
||||
func TestDeleteEventBySerial(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
signer, err := p8k.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create signer: %v", err)
|
||||
}
|
||||
if err := signer.Generate(); err != nil {
|
||||
t.Fatalf("Failed to generate keypair: %v", err)
|
||||
}
|
||||
|
||||
// Create and save event
|
||||
ev := event.New()
|
||||
ev.Pubkey = signer.Pub()
|
||||
ev.CreatedAt = timestamp.Now().V
|
||||
ev.Kind = 1
|
||||
ev.Content = []byte("Event to be deleted by serial")
|
||||
|
||||
if err := ev.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, ev); err != nil {
|
||||
t.Fatalf("Failed to save event: %v", err)
|
||||
}
|
||||
|
||||
// Get serial
|
||||
serial, err := db.GetSerialById(ev.ID[:])
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get serial: %v", err)
|
||||
}
|
||||
|
||||
// Delete by serial
|
||||
if err := db.DeleteEventBySerial(ctx, serial, ev); err != nil {
|
||||
t.Fatalf("Failed to delete event by serial: %v", err)
|
||||
}
|
||||
|
||||
// Verify event is deleted
|
||||
evs, err := db.QueryEvents(ctx, &filter.F{
|
||||
Ids: tag.NewFromBytesSlice(ev.ID),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query after deletion: %v", err)
|
||||
}
|
||||
if len(evs) != 0 {
|
||||
t.Fatalf("Expected 0 events after deletion, got %d", len(evs))
|
||||
}
|
||||
|
||||
t.Logf("✓ DeleteEventBySerial successfully removed event")
|
||||
}
|
||||
|
||||
func TestProcessDelete_AuthorCanDeleteOwnEvent(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
signer, err := p8k.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create signer: %v", err)
|
||||
}
|
||||
if err := signer.Generate(); err != nil {
|
||||
t.Fatalf("Failed to generate keypair: %v", err)
|
||||
}
|
||||
|
||||
// Create and save original event
|
||||
originalEvent := event.New()
|
||||
originalEvent.Pubkey = signer.Pub()
|
||||
originalEvent.CreatedAt = timestamp.Now().V
|
||||
originalEvent.Kind = 1
|
||||
originalEvent.Content = []byte("This event will be deleted via kind 5")
|
||||
|
||||
if err := originalEvent.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, originalEvent); err != nil {
|
||||
t.Fatalf("Failed to save event: %v", err)
|
||||
}
|
||||
|
||||
// Create kind 5 deletion event
|
||||
deleteEvent := event.New()
|
||||
deleteEvent.Pubkey = signer.Pub() // Same author
|
||||
deleteEvent.CreatedAt = timestamp.Now().V + 1
|
||||
deleteEvent.Kind = kind.Deletion.K
|
||||
deleteEvent.Content = []byte("Deleting my event")
|
||||
deleteEvent.Tags = tag.NewS(
|
||||
tag.NewFromAny("e", hex.Enc(originalEvent.ID[:])),
|
||||
)
|
||||
|
||||
if err := deleteEvent.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign delete event: %v", err)
|
||||
}
|
||||
|
||||
// Process deletion (no admins)
|
||||
if err := db.ProcessDelete(deleteEvent, nil); err != nil {
|
||||
t.Fatalf("Failed to process delete: %v", err)
|
||||
}
|
||||
|
||||
// Verify original event is deleted
|
||||
evs, err := db.QueryEvents(ctx, &filter.F{
|
||||
Ids: tag.NewFromBytesSlice(originalEvent.ID),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query after deletion: %v", err)
|
||||
}
|
||||
if len(evs) != 0 {
|
||||
t.Fatalf("Expected 0 events after deletion, got %d", len(evs))
|
||||
}
|
||||
|
||||
t.Logf("✓ ProcessDelete allowed author to delete own event")
|
||||
}
|
||||
|
||||
func TestProcessDelete_OtherUserCannotDelete(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
alice, _ := p8k.New()
|
||||
alice.Generate()
|
||||
|
||||
bob, _ := p8k.New()
|
||||
bob.Generate()
|
||||
|
||||
// Alice creates an event
|
||||
aliceEvent := event.New()
|
||||
aliceEvent.Pubkey = alice.Pub()
|
||||
aliceEvent.CreatedAt = timestamp.Now().V
|
||||
aliceEvent.Kind = 1
|
||||
aliceEvent.Content = []byte("Alice's event")
|
||||
|
||||
if err := aliceEvent.Sign(alice); err != nil {
|
||||
t.Fatalf("Failed to sign event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, aliceEvent); err != nil {
|
||||
t.Fatalf("Failed to save event: %v", err)
|
||||
}
|
||||
|
||||
// Bob tries to delete Alice's event
|
||||
deleteEvent := event.New()
|
||||
deleteEvent.Pubkey = bob.Pub() // Different author
|
||||
deleteEvent.CreatedAt = timestamp.Now().V + 1
|
||||
deleteEvent.Kind = kind.Deletion.K
|
||||
deleteEvent.Tags = tag.NewS(
|
||||
tag.NewFromAny("e", hex.Enc(aliceEvent.ID[:])),
|
||||
)
|
||||
|
||||
if err := deleteEvent.Sign(bob); err != nil {
|
||||
t.Fatalf("Failed to sign delete event: %v", err)
|
||||
}
|
||||
|
||||
// Process deletion (Bob is not an admin)
|
||||
_ = db.ProcessDelete(deleteEvent, nil)
|
||||
|
||||
// Verify Alice's event still exists
|
||||
evs, err := db.QueryEvents(ctx, &filter.F{
|
||||
Ids: tag.NewFromBytesSlice(aliceEvent.ID),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query: %v", err)
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
t.Fatalf("Expected Alice's event to still exist, got %d events", len(evs))
|
||||
}
|
||||
|
||||
t.Logf("✓ ProcessDelete correctly prevented unauthorized deletion")
|
||||
}
|
||||
|
||||
func TestProcessDelete_AdminCanDeleteAnyEvent(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
alice, _ := p8k.New()
|
||||
alice.Generate()
|
||||
|
||||
admin, _ := p8k.New()
|
||||
admin.Generate()
|
||||
|
||||
// Alice creates an event
|
||||
aliceEvent := event.New()
|
||||
aliceEvent.Pubkey = alice.Pub()
|
||||
aliceEvent.CreatedAt = timestamp.Now().V
|
||||
aliceEvent.Kind = 1
|
||||
aliceEvent.Content = []byte("Alice's event to be deleted by admin")
|
||||
|
||||
if err := aliceEvent.Sign(alice); err != nil {
|
||||
t.Fatalf("Failed to sign event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, aliceEvent); err != nil {
|
||||
t.Fatalf("Failed to save event: %v", err)
|
||||
}
|
||||
|
||||
// Admin creates deletion event
|
||||
deleteEvent := event.New()
|
||||
deleteEvent.Pubkey = admin.Pub()
|
||||
deleteEvent.CreatedAt = timestamp.Now().V + 1
|
||||
deleteEvent.Kind = kind.Deletion.K
|
||||
deleteEvent.Tags = tag.NewS(
|
||||
tag.NewFromAny("e", hex.Enc(aliceEvent.ID[:])),
|
||||
)
|
||||
|
||||
if err := deleteEvent.Sign(admin); err != nil {
|
||||
t.Fatalf("Failed to sign delete event: %v", err)
|
||||
}
|
||||
|
||||
// Process deletion with admin pubkey
|
||||
adminPubkeys := [][]byte{admin.Pub()}
|
||||
if err := db.ProcessDelete(deleteEvent, adminPubkeys); err != nil {
|
||||
t.Fatalf("Failed to process delete: %v", err)
|
||||
}
|
||||
|
||||
// Verify Alice's event is deleted
|
||||
evs, err := db.QueryEvents(ctx, &filter.F{
|
||||
Ids: tag.NewFromBytesSlice(aliceEvent.ID),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query: %v", err)
|
||||
}
|
||||
if len(evs) != 0 {
|
||||
t.Fatalf("Expected Alice's event to be deleted, got %d events", len(evs))
|
||||
}
|
||||
|
||||
t.Logf("✓ ProcessDelete allowed admin to delete event")
|
||||
}
|
||||
|
||||
func TestCheckForDeleted(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
signer, err := p8k.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create signer: %v", err)
|
||||
}
|
||||
if err := signer.Generate(); err != nil {
|
||||
t.Fatalf("Failed to generate keypair: %v", err)
|
||||
}
|
||||
|
||||
// Create target event
|
||||
targetEvent := event.New()
|
||||
targetEvent.Pubkey = signer.Pub()
|
||||
targetEvent.CreatedAt = timestamp.Now().V
|
||||
targetEvent.Kind = 1
|
||||
targetEvent.Content = []byte("Target event")
|
||||
|
||||
if err := targetEvent.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign target event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, targetEvent); err != nil {
|
||||
t.Fatalf("Failed to save target event: %v", err)
|
||||
}
|
||||
|
||||
// Check that event is not deleted (no deletion event exists)
|
||||
err = db.CheckForDeleted(targetEvent, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for non-deleted event, got: %v", err)
|
||||
}
|
||||
|
||||
// Create deletion event that references target
|
||||
deleteEvent := event.New()
|
||||
deleteEvent.Pubkey = signer.Pub()
|
||||
deleteEvent.CreatedAt = timestamp.Now().V + 1
|
||||
deleteEvent.Kind = kind.Deletion.K
|
||||
deleteEvent.Tags = tag.NewS(
|
||||
tag.NewFromAny("e", hex.Enc(targetEvent.ID[:])),
|
||||
)
|
||||
|
||||
if err := deleteEvent.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign delete event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, deleteEvent); err != nil {
|
||||
t.Fatalf("Failed to save delete event: %v", err)
|
||||
}
|
||||
|
||||
// Now check should return error (event has been deleted)
|
||||
err = db.CheckForDeleted(targetEvent, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for deleted event")
|
||||
}
|
||||
|
||||
t.Logf("✓ CheckForDeleted correctly detected deletion event")
|
||||
}
|
||||
|
||||
func TestReplaceableEventDeletion(t *testing.T) {
|
||||
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
||||
if neo4jURI == "" {
|
||||
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
db, err := New(ctx, cancel, tempDir, "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
<-db.Ready()
|
||||
|
||||
if err := db.Wipe(); err != nil {
|
||||
t.Fatalf("Failed to wipe database: %v", err)
|
||||
}
|
||||
|
||||
signer, err := p8k.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create signer: %v", err)
|
||||
}
|
||||
if err := signer.Generate(); err != nil {
|
||||
t.Fatalf("Failed to generate keypair: %v", err)
|
||||
}
|
||||
|
||||
// Create replaceable event (kind 0 - profile)
|
||||
profileEvent := event.New()
|
||||
profileEvent.Pubkey = signer.Pub()
|
||||
profileEvent.CreatedAt = timestamp.Now().V
|
||||
profileEvent.Kind = 0
|
||||
profileEvent.Content = []byte(`{"name":"Test User"}`)
|
||||
|
||||
if err := profileEvent.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, profileEvent); err != nil {
|
||||
t.Fatalf("Failed to save event: %v", err)
|
||||
}
|
||||
|
||||
// Verify event exists
|
||||
evs, err := db.QueryEvents(ctx, &filter.F{
|
||||
Kinds: kind.NewS(kind.New(0)),
|
||||
Authors: tag.NewFromBytesSlice(signer.Pub()),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query: %v", err)
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
t.Fatalf("Expected 1 profile event, got %d", len(evs))
|
||||
}
|
||||
|
||||
// Create a newer replaceable event (replaces the old one)
|
||||
newerProfileEvent := event.New()
|
||||
newerProfileEvent.Pubkey = signer.Pub()
|
||||
newerProfileEvent.CreatedAt = timestamp.Now().V + 100
|
||||
newerProfileEvent.Kind = 0
|
||||
newerProfileEvent.Content = []byte(`{"name":"Updated User"}`)
|
||||
|
||||
if err := newerProfileEvent.Sign(signer); err != nil {
|
||||
t.Fatalf("Failed to sign newer event: %v", err)
|
||||
}
|
||||
|
||||
if _, err := db.SaveEvent(ctx, newerProfileEvent); err != nil {
|
||||
t.Fatalf("Failed to save newer event: %v", err)
|
||||
}
|
||||
|
||||
// Query should return only the newer event
|
||||
evs, err = db.QueryEvents(ctx, &filter.F{
|
||||
Kinds: kind.NewS(kind.New(0)),
|
||||
Authors: tag.NewFromBytesSlice(signer.Pub()),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query: %v", err)
|
||||
}
|
||||
if len(evs) != 1 {
|
||||
t.Fatalf("Expected 1 profile event after replacement, got %d", len(evs))
|
||||
}
|
||||
|
||||
if hex.Enc(evs[0].ID[:]) != hex.Enc(newerProfileEvent.ID[:]) {
|
||||
t.Fatal("Expected newer profile event to be returned")
|
||||
}
|
||||
|
||||
t.Logf("✓ Replaceable event correctly replaced by newer version")
|
||||
}
|
||||
Reference in New Issue
Block a user