Replaces outdated Neo4j test setup with a robust TestMain, shared test database, and utility functions for test data and migrations. Improves Cypher generation for processing e-tags, p-tags, and other tags to ensure compliance with Neo4j syntax. Added integration test script and updated benchmark reports for Badger backend.
303 lines
8.7 KiB
Go
303 lines
8.7 KiB
Go
package neo4j
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// TestMigrationV2_CleanupBinaryEncodedValues tests that migration v2 properly
|
|
// cleans up binary-encoded pubkeys and event IDs
|
|
func TestMigrationV2_CleanupBinaryEncodedValues(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up before test
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create some valid NostrUser nodes (should NOT be deleted)
|
|
validPubkeys := []string{
|
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
|
}
|
|
for _, pk := range validPubkeys {
|
|
setupInvalidNostrUser(t, pk) // Using setupInvalidNostrUser to create directly
|
|
}
|
|
|
|
// Create some invalid NostrUser nodes (should be deleted)
|
|
invalidPubkeys := []string{
|
|
"binary\x00garbage\x00data", // Binary garbage
|
|
"ABCDEF", // Too short
|
|
"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG", // Non-hex chars
|
|
string(append(make([]byte, 32), 0)), // 33-byte binary format
|
|
}
|
|
for _, pk := range invalidPubkeys {
|
|
setupInvalidNostrUser(t, pk)
|
|
}
|
|
|
|
// Verify invalid nodes exist before migration
|
|
invalidCountBefore := countInvalidNostrUsers(t)
|
|
if invalidCountBefore != 4 {
|
|
t.Errorf("Expected 4 invalid NostrUsers before migration, got %d", invalidCountBefore)
|
|
}
|
|
|
|
totalBefore := countNodes(t, "NostrUser")
|
|
if totalBefore != 6 {
|
|
t.Errorf("Expected 6 total NostrUsers before migration, got %d", totalBefore)
|
|
}
|
|
|
|
// Run the migration
|
|
err := migrateBinaryToHex(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("Migration failed: %v", err)
|
|
}
|
|
|
|
// Verify invalid nodes were deleted
|
|
invalidCountAfter := countInvalidNostrUsers(t)
|
|
if invalidCountAfter != 0 {
|
|
t.Errorf("Expected 0 invalid NostrUsers after migration, got %d", invalidCountAfter)
|
|
}
|
|
|
|
// Verify valid nodes were NOT deleted
|
|
totalAfter := countNodes(t, "NostrUser")
|
|
if totalAfter != 2 {
|
|
t.Errorf("Expected 2 valid NostrUsers after migration, got %d", totalAfter)
|
|
}
|
|
}
|
|
|
|
// TestMigrationV2_CleanupInvalidEvents tests that migration v2 properly
|
|
// cleans up Event nodes with invalid pubkeys or IDs
|
|
func TestMigrationV2_CleanupInvalidEvents(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up before test
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create valid events
|
|
validEventID := "1111111111111111111111111111111111111111111111111111111111111111"
|
|
validPubkey := "0000000000000000000000000000000000000000000000000000000000000001"
|
|
setupTestEvent(t, validEventID, validPubkey, 1, "[]")
|
|
|
|
// Create invalid events directly
|
|
setupInvalidEvent(t, "invalid_id", validPubkey) // Invalid ID
|
|
setupInvalidEvent(t, validEventID+"2", "invalid_pubkey") // Invalid pubkey (different ID to avoid duplicate)
|
|
setupInvalidEvent(t, "TOOSHORT", "binary\x00garbage") // Both invalid
|
|
|
|
// Count events before migration
|
|
eventsBefore := countNodes(t, "Event")
|
|
if eventsBefore != 4 {
|
|
t.Errorf("Expected 4 Events before migration, got %d", eventsBefore)
|
|
}
|
|
|
|
// Run the migration
|
|
err := migrateBinaryToHex(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("Migration failed: %v", err)
|
|
}
|
|
|
|
// Verify only valid event remains
|
|
eventsAfter := countNodes(t, "Event")
|
|
if eventsAfter != 1 {
|
|
t.Errorf("Expected 1 valid Event after migration, got %d", eventsAfter)
|
|
}
|
|
}
|
|
|
|
// TestMigrationV2_CleanupInvalidTags tests that migration v2 properly
|
|
// cleans up Tag nodes (e/p type) with invalid values
|
|
func TestMigrationV2_CleanupInvalidTags(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up before test
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create valid tags
|
|
validHex := "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
|
setupInvalidTag(t, "e", validHex) // Valid e-tag
|
|
setupInvalidTag(t, "p", validHex) // Valid p-tag
|
|
setupInvalidTag(t, "t", "topic") // Non e/p tag (should not be affected)
|
|
|
|
// Create invalid e/p tags
|
|
setupInvalidTag(t, "e", "binary\x00garbage") // Invalid e-tag
|
|
setupInvalidTag(t, "p", "TOOSHORT") // Invalid p-tag (too short)
|
|
setupInvalidTag(t, "e", string(append(make([]byte, 32), 0))) // Binary encoded
|
|
|
|
// Count tags before migration
|
|
tagsBefore := countNodes(t, "Tag")
|
|
if tagsBefore != 6 {
|
|
t.Errorf("Expected 6 Tags before migration, got %d", tagsBefore)
|
|
}
|
|
|
|
invalidBefore := countInvalidTags(t)
|
|
if invalidBefore != 3 {
|
|
t.Errorf("Expected 3 invalid e/p Tags before migration, got %d", invalidBefore)
|
|
}
|
|
|
|
// Run the migration
|
|
err := migrateBinaryToHex(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("Migration failed: %v", err)
|
|
}
|
|
|
|
// Verify invalid tags were deleted
|
|
invalidAfter := countInvalidTags(t)
|
|
if invalidAfter != 0 {
|
|
t.Errorf("Expected 0 invalid e/p Tags after migration, got %d", invalidAfter)
|
|
}
|
|
|
|
// Verify valid tags remain (2 e/p valid + 1 t-tag)
|
|
tagsAfter := countNodes(t, "Tag")
|
|
if tagsAfter != 3 {
|
|
t.Errorf("Expected 3 Tags after migration, got %d", tagsAfter)
|
|
}
|
|
}
|
|
|
|
// TestMigrationV2_Idempotent tests that migration v2 can be run multiple times safely
|
|
func TestMigrationV2_Idempotent(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up before test
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create only valid data
|
|
validPubkey := "0000000000000000000000000000000000000000000000000000000000000001"
|
|
validEventID := "1111111111111111111111111111111111111111111111111111111111111111"
|
|
setupTestEvent(t, validEventID, validPubkey, 1, "[]")
|
|
|
|
countBefore := countNodes(t, "Event")
|
|
|
|
// Run migration first time
|
|
err := migrateBinaryToHex(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("First migration run failed: %v", err)
|
|
}
|
|
|
|
countAfterFirst := countNodes(t, "Event")
|
|
if countAfterFirst != countBefore {
|
|
t.Errorf("First migration changed valid event count: before=%d, after=%d", countBefore, countAfterFirst)
|
|
}
|
|
|
|
// Run migration second time
|
|
err = migrateBinaryToHex(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("Second migration run failed: %v", err)
|
|
}
|
|
|
|
countAfterSecond := countNodes(t, "Event")
|
|
if countAfterSecond != countBefore {
|
|
t.Errorf("Second migration changed valid event count: before=%d, after=%d", countBefore, countAfterSecond)
|
|
}
|
|
}
|
|
|
|
// TestMigrationV2_NoDataDoesNotFail tests that migration v2 succeeds with empty database
|
|
func TestMigrationV2_NoDataDoesNotFail(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up completely
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Run migration on empty database - should not fail
|
|
err := migrateBinaryToHex(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("Migration on empty database failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestMigrationMarking tests that migrations are properly tracked
|
|
func TestMigrationMarking(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up before test
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Verify migration v2 has not been applied
|
|
if testDB.migrationApplied(ctx, "v2") {
|
|
t.Error("Migration v2 should not be applied before test")
|
|
}
|
|
|
|
// Mark migration as complete
|
|
err := testDB.markMigrationComplete(ctx, "v2", "Test migration")
|
|
if err != nil {
|
|
t.Fatalf("Failed to mark migration complete: %v", err)
|
|
}
|
|
|
|
// Verify migration is now marked as applied
|
|
if !testDB.migrationApplied(ctx, "v2") {
|
|
t.Error("Migration v2 should be applied after marking")
|
|
}
|
|
|
|
// Clean up
|
|
cleanTestDatabase()
|
|
}
|
|
|
|
// TestMigrationV1_AuthorToNostrUserMerge tests the author migration
|
|
func TestMigrationV1_AuthorToNostrUserMerge(t *testing.T) {
|
|
if testDB == nil {
|
|
t.Skip("Neo4j not available")
|
|
}
|
|
|
|
// Clean up before test
|
|
cleanTestDatabase()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create some Author nodes (legacy format)
|
|
authorPubkeys := []string{
|
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
|
}
|
|
|
|
for _, pk := range authorPubkeys {
|
|
cypher := `CREATE (a:Author {pubkey: $pubkey})`
|
|
_, err := testDB.ExecuteWrite(ctx, cypher, map[string]any{"pubkey": pk})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create Author node: %v", err)
|
|
}
|
|
}
|
|
|
|
// Verify Author nodes exist
|
|
authorCount := countNodes(t, "Author")
|
|
if authorCount != 2 {
|
|
t.Errorf("Expected 2 Author nodes, got %d", authorCount)
|
|
}
|
|
|
|
// Run migration
|
|
err := migrateAuthorToNostrUser(ctx, testDB)
|
|
if err != nil {
|
|
t.Fatalf("Migration failed: %v", err)
|
|
}
|
|
|
|
// Verify NostrUser nodes were created
|
|
nostrUserCount := countNodes(t, "NostrUser")
|
|
if nostrUserCount != 2 {
|
|
t.Errorf("Expected 2 NostrUser nodes after migration, got %d", nostrUserCount)
|
|
}
|
|
|
|
// Verify Author nodes were deleted (they should have no relationships after migration)
|
|
authorCountAfter := countNodes(t, "Author")
|
|
if authorCountAfter != 0 {
|
|
t.Errorf("Expected 0 Author nodes after migration, got %d", authorCountAfter)
|
|
}
|
|
}
|