You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
14 KiB
555 lines
14 KiB
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") |
|
}
|
|
|