453 lines
12 KiB
Go
453 lines
12 KiB
Go
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"
|
|
)
|
|
|
|
// setupTestDatabase creates a fresh Neo4j database connection for testing
|
|
func setupTestDatabase(t *testing.T) (*N, context.Context, context.CancelFunc) {
|
|
t.Helper()
|
|
|
|
neo4jURI := os.Getenv("ORLY_NEO4J_URI")
|
|
if neo4jURI == "" {
|
|
t.Skip("Skipping Neo4j test: ORLY_NEO4J_URI not set")
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
tempDir := t.TempDir()
|
|
db, err := New(ctx, cancel, tempDir, "debug")
|
|
if err != nil {
|
|
cancel()
|
|
t.Fatalf("Failed to create database: %v", err)
|
|
}
|
|
|
|
<-db.Ready()
|
|
|
|
if err := db.Wipe(); err != nil {
|
|
db.Close()
|
|
cancel()
|
|
t.Fatalf("Failed to wipe database: %v", err)
|
|
}
|
|
|
|
return db, ctx, cancel
|
|
}
|
|
|
|
// createTestSigner creates a new signer for test events
|
|
func createTestSigner(t *testing.T) *p8k.Signer {
|
|
t.Helper()
|
|
|
|
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)
|
|
}
|
|
return signer
|
|
}
|
|
|
|
// createAndSaveEvent creates a signed event and saves it to the database
|
|
func createAndSaveEvent(t *testing.T, ctx context.Context, db *N, signer *p8k.Signer, k uint16, content string, tags *tag.S, ts int64) *event.E {
|
|
t.Helper()
|
|
|
|
ev := event.New()
|
|
ev.Pubkey = signer.Pub()
|
|
ev.CreatedAt = ts
|
|
ev.Kind = k
|
|
ev.Content = []byte(content)
|
|
ev.Tags = tags
|
|
|
|
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)
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
func TestQueryEventsByID(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
|
|
// Create and save a test event
|
|
ev := createAndSaveEvent(t, ctx, db, signer, 1, "Test event for ID query", nil, timestamp.Now().V)
|
|
|
|
// Query by ID
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Ids: tag.NewFromBytesSlice(ev.ID),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events by ID: %v", err)
|
|
}
|
|
|
|
if len(evs) != 1 {
|
|
t.Fatalf("Expected 1 event, got %d", len(evs))
|
|
}
|
|
|
|
if hex.Enc(evs[0].ID[:]) != hex.Enc(ev.ID[:]) {
|
|
t.Fatalf("Event ID mismatch: got %s, expected %s",
|
|
hex.Enc(evs[0].ID[:]), hex.Enc(ev.ID[:]))
|
|
}
|
|
|
|
t.Logf("✓ Query by ID returned correct event")
|
|
}
|
|
|
|
func TestQueryEventsByKind(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events of different kinds
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Kind 1 event A", nil, baseTs)
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Kind 1 event B", nil, baseTs+1)
|
|
createAndSaveEvent(t, ctx, db, signer, 7, "Kind 7 reaction", nil, baseTs+2)
|
|
createAndSaveEvent(t, ctx, db, signer, 30023, "Kind 30023 article", nil, baseTs+3)
|
|
|
|
// Query for kind 1
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(1)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events by kind: %v", err)
|
|
}
|
|
|
|
if len(evs) != 2 {
|
|
t.Fatalf("Expected 2 kind 1 events, got %d", len(evs))
|
|
}
|
|
|
|
for _, ev := range evs {
|
|
if ev.Kind != 1 {
|
|
t.Fatalf("Expected kind 1, got %d", ev.Kind)
|
|
}
|
|
}
|
|
|
|
t.Logf("✓ Query by kind returned %d correct events", len(evs))
|
|
}
|
|
|
|
func TestQueryEventsByAuthor(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
alice := createTestSigner(t)
|
|
bob := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events from different authors
|
|
createAndSaveEvent(t, ctx, db, alice, 1, "Alice's event 1", nil, baseTs)
|
|
createAndSaveEvent(t, ctx, db, alice, 1, "Alice's event 2", nil, baseTs+1)
|
|
createAndSaveEvent(t, ctx, db, bob, 1, "Bob's event", nil, baseTs+2)
|
|
|
|
// Query for Alice's events
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Authors: tag.NewFromBytesSlice(alice.Pub()),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events by author: %v", err)
|
|
}
|
|
|
|
if len(evs) != 2 {
|
|
t.Fatalf("Expected 2 events from Alice, got %d", len(evs))
|
|
}
|
|
|
|
alicePubkey := hex.Enc(alice.Pub())
|
|
for _, ev := range evs {
|
|
if hex.Enc(ev.Pubkey[:]) != alicePubkey {
|
|
t.Fatalf("Expected author %s, got %s", alicePubkey, hex.Enc(ev.Pubkey[:]))
|
|
}
|
|
}
|
|
|
|
t.Logf("✓ Query by author returned %d correct events", len(evs))
|
|
}
|
|
|
|
func TestQueryEventsByTimeRange(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events at different times
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Old event", nil, baseTs-7200) // 2 hours ago
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Recent event", nil, baseTs-1800) // 30 min ago
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Current event", nil, baseTs)
|
|
|
|
// Query for events in the last hour
|
|
since := ×tamp.T{V: baseTs - 3600}
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Since: since,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events by time range: %v", err)
|
|
}
|
|
|
|
if len(evs) != 2 {
|
|
t.Fatalf("Expected 2 events in last hour, got %d", len(evs))
|
|
}
|
|
|
|
for _, ev := range evs {
|
|
if ev.CreatedAt < since.V {
|
|
t.Fatalf("Event created_at %d is before since %d", ev.CreatedAt, since.V)
|
|
}
|
|
}
|
|
|
|
t.Logf("✓ Query by time range returned %d correct events", len(evs))
|
|
}
|
|
|
|
func TestQueryEventsByTag(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events with tags
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Bitcoin post",
|
|
tag.NewS(tag.NewFromAny("t", "bitcoin")), baseTs)
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Nostr post",
|
|
tag.NewS(tag.NewFromAny("t", "nostr")), baseTs+1)
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Bitcoin and Nostr post",
|
|
tag.NewS(tag.NewFromAny("t", "bitcoin"), tag.NewFromAny("t", "nostr")), baseTs+2)
|
|
|
|
// Query for bitcoin tagged events
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Tags: tag.NewS(tag.NewFromAny("t", "bitcoin")),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events by tag: %v", err)
|
|
}
|
|
|
|
if len(evs) != 2 {
|
|
t.Fatalf("Expected 2 bitcoin-tagged events, got %d", len(evs))
|
|
}
|
|
|
|
t.Logf("✓ Query by tag returned %d correct events", len(evs))
|
|
}
|
|
|
|
func TestQueryEventsByKindAndAuthor(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
alice := createTestSigner(t)
|
|
bob := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events
|
|
createAndSaveEvent(t, ctx, db, alice, 1, "Alice note", nil, baseTs)
|
|
createAndSaveEvent(t, ctx, db, alice, 7, "Alice reaction", nil, baseTs+1)
|
|
createAndSaveEvent(t, ctx, db, bob, 1, "Bob note", nil, baseTs+2)
|
|
|
|
// Query for Alice's kind 1 events
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(1)),
|
|
Authors: tag.NewFromBytesSlice(alice.Pub()),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events by kind and author: %v", err)
|
|
}
|
|
|
|
if len(evs) != 1 {
|
|
t.Fatalf("Expected 1 kind 1 event from Alice, got %d", len(evs))
|
|
}
|
|
|
|
t.Logf("✓ Query by kind and author returned correct events")
|
|
}
|
|
|
|
func TestQueryEventsWithLimit(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create many events
|
|
for i := 0; i < 20; i++ {
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Event", nil, baseTs+int64(i))
|
|
}
|
|
|
|
// Query with limit
|
|
limit := 5
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(1)),
|
|
Limit: limit,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events with limit: %v", err)
|
|
}
|
|
|
|
if len(evs) != limit {
|
|
t.Fatalf("Expected %d events with limit, got %d", limit, len(evs))
|
|
}
|
|
|
|
t.Logf("✓ Query with limit returned %d events", len(evs))
|
|
}
|
|
|
|
func TestQueryEventsOrderByCreatedAt(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events at different times
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "First", nil, baseTs)
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Second", nil, baseTs+100)
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Third", nil, baseTs+200)
|
|
|
|
// Query and verify order (should be descending by created_at)
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(1)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events: %v", err)
|
|
}
|
|
|
|
if len(evs) < 2 {
|
|
t.Fatalf("Expected at least 2 events, got %d", len(evs))
|
|
}
|
|
|
|
// Verify descending order
|
|
for i := 1; i < len(evs); i++ {
|
|
if evs[i-1].CreatedAt < evs[i].CreatedAt {
|
|
t.Fatalf("Events not in descending order: %d < %d at index %d",
|
|
evs[i-1].CreatedAt, evs[i].CreatedAt, i)
|
|
}
|
|
}
|
|
|
|
t.Logf("✓ Query returned events in correct descending order")
|
|
}
|
|
|
|
func TestQueryEventsEmpty(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
// Query for non-existent kind
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(99999)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events: %v", err)
|
|
}
|
|
|
|
if len(evs) != 0 {
|
|
t.Fatalf("Expected 0 events, got %d", len(evs))
|
|
}
|
|
|
|
t.Logf("✓ Query for non-existent kind returned empty result")
|
|
}
|
|
|
|
func TestQueryEventsMultipleKinds(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events of different kinds
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Note", nil, baseTs)
|
|
createAndSaveEvent(t, ctx, db, signer, 7, "Reaction", nil, baseTs+1)
|
|
createAndSaveEvent(t, ctx, db, signer, 30023, "Article", nil, baseTs+2)
|
|
|
|
// Query for multiple kinds
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(1), kind.New(7)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events: %v", err)
|
|
}
|
|
|
|
if len(evs) != 2 {
|
|
t.Fatalf("Expected 2 events (kind 1 and 7), got %d", len(evs))
|
|
}
|
|
|
|
t.Logf("✓ Query for multiple kinds returned correct events")
|
|
}
|
|
|
|
func TestQueryEventsMultipleAuthors(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
alice := createTestSigner(t)
|
|
bob := createTestSigner(t)
|
|
charlie := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events from different authors
|
|
createAndSaveEvent(t, ctx, db, alice, 1, "Alice", nil, baseTs)
|
|
createAndSaveEvent(t, ctx, db, bob, 1, "Bob", nil, baseTs+1)
|
|
createAndSaveEvent(t, ctx, db, charlie, 1, "Charlie", nil, baseTs+2)
|
|
|
|
// Query for Alice and Bob's events
|
|
authors := tag.NewFromBytesSlice(alice.Pub())
|
|
authors.Append(tag.NewFromBytesSlice(bob.Pub()).GetFirst(nil))
|
|
|
|
evs, err := db.QueryEvents(ctx, &filter.F{
|
|
Authors: authors,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to query events: %v", err)
|
|
}
|
|
|
|
if len(evs) != 2 {
|
|
t.Fatalf("Expected 2 events from Alice and Bob, got %d", len(evs))
|
|
}
|
|
|
|
t.Logf("✓ Query for multiple authors returned correct events")
|
|
}
|
|
|
|
func TestCountEvents(t *testing.T) {
|
|
db, ctx, cancel := setupTestDatabase(t)
|
|
defer db.Close()
|
|
defer cancel()
|
|
|
|
signer := createTestSigner(t)
|
|
baseTs := timestamp.Now().V
|
|
|
|
// Create events
|
|
for i := 0; i < 5; i++ {
|
|
createAndSaveEvent(t, ctx, db, signer, 1, "Event", nil, baseTs+int64(i))
|
|
}
|
|
|
|
// Count events
|
|
count, err := db.CountEvents(ctx, &filter.F{
|
|
Kinds: kind.NewS(kind.New(1)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to count events: %v", err)
|
|
}
|
|
|
|
if count != 5 {
|
|
t.Fatalf("Expected count 5, got %d", count)
|
|
}
|
|
|
|
t.Logf("✓ Count events returned correct count: %d", count)
|
|
}
|