package store import ( "errors" "fmt" "sync" "sync/atomic" "testing" "time" "git.flowmade.one/flowmade-one/aether" ) // === Event Store Tests === func TestNewInMemoryEventStore(t *testing.T) { store := NewInMemoryEventStore() if store == nil { t.Fatal("NewInMemoryEventStore returned nil") } if store.events == nil { t.Error("events map is nil") } } func TestSaveEvent_SingleEvent(t *testing.T) { store := NewInMemoryEventStore() event := &aether.Event{ ID: "evt-123", EventType: "OrderPlaced", ActorID: "order-456", Version: 1, Data: map[string]interface{}{ "total": 100.50, }, Timestamp: time.Now(), } err := store.SaveEvent(event) if err != nil { t.Fatalf("SaveEvent failed: %v", err) } // Verify event was persisted events, err := store.GetEvents("order-456", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 1 { t.Fatalf("expected 1 event, got %d", len(events)) } if events[0].ID != "evt-123" { t.Errorf("event ID mismatch: got %q, want %q", events[0].ID, "evt-123") } } func TestSaveEvent_MultipleEvents(t *testing.T) { store := NewInMemoryEventStore() for i := 1; i <= 5; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "OrderUpdated", ActorID: "order-456", Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed for event %d: %v", i, err) } } events, err := store.GetEvents("order-456", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 5 { t.Errorf("expected 5 events, got %d", len(events)) } } func TestSaveEvent_MultipleActors(t *testing.T) { store := NewInMemoryEventStore() // Save events for different actors actors := []string{"actor-1", "actor-2", "actor-3"} for _, actorID := range actors { for i := 1; i <= 3; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%s-%d", actorID, i), EventType: "TestEvent", ActorID: actorID, Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } } } // Verify each actor has its own events for _, actorID := range actors { events, err := store.GetEvents(actorID, 0) if err != nil { t.Fatalf("GetEvents failed for %s: %v", actorID, err) } if len(events) != 3 { t.Errorf("expected 3 events for %s, got %d", actorID, len(events)) } for _, event := range events { if event.ActorID != actorID { t.Errorf("event has wrong ActorID: got %q, want %q", event.ActorID, actorID) } } } } func TestSaveEvent_PreservesAllFields(t *testing.T) { store := NewInMemoryEventStore() ts := time.Date(2026, 1, 9, 12, 0, 0, 0, time.UTC) event := &aether.Event{ ID: "evt-123", EventType: "OrderPlaced", ActorID: "order-456", CommandID: "cmd-789", Version: 42, Data: map[string]interface{}{ "total": 100.50, "currency": "USD", }, Timestamp: ts, } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } events, err := store.GetEvents("order-456", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } retrieved := events[0] if retrieved.ID != event.ID { t.Errorf("ID mismatch: got %q, want %q", retrieved.ID, event.ID) } if retrieved.EventType != event.EventType { t.Errorf("EventType mismatch: got %q, want %q", retrieved.EventType, event.EventType) } if retrieved.ActorID != event.ActorID { t.Errorf("ActorID mismatch: got %q, want %q", retrieved.ActorID, event.ActorID) } if retrieved.CommandID != event.CommandID { t.Errorf("CommandID mismatch: got %q, want %q", retrieved.CommandID, event.CommandID) } if retrieved.Version != event.Version { t.Errorf("Version mismatch: got %d, want %d", retrieved.Version, event.Version) } if !retrieved.Timestamp.Equal(event.Timestamp) { t.Errorf("Timestamp mismatch: got %v, want %v", retrieved.Timestamp, event.Timestamp) } if retrieved.Data["total"] != event.Data["total"] { t.Errorf("Data.total mismatch: got %v, want %v", retrieved.Data["total"], event.Data["total"]) } if retrieved.Data["currency"] != event.Data["currency"] { t.Errorf("Data.currency mismatch: got %v, want %v", retrieved.Data["currency"], event.Data["currency"]) } } func TestGetEvents_RetrievesInOrder(t *testing.T) { store := NewInMemoryEventStore() // Save events in order for i := 1; i <= 10; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-123", Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } } events, err := store.GetEvents("actor-123", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } // Verify events are returned in insertion order for i, event := range events { expectedID := fmt.Sprintf("evt-%d", i+1) if event.ID != expectedID { t.Errorf("event %d: got ID %q, want %q", i, event.ID, expectedID) } expectedVersion := int64(i + 1) if event.Version != expectedVersion { t.Errorf("event %d: got Version %d, want %d", i, event.Version, expectedVersion) } } } func TestGetEvents_FromVersionFilters(t *testing.T) { store := NewInMemoryEventStore() // Save events with versions 1-10 for i := 1; i <= 10; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-123", Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } } testCases := []struct { name string fromVersion int64 expected int minVersion int64 }{ {"from version 0", 0, 10, 1}, {"from version 1", 1, 10, 1}, {"from version 5", 5, 6, 5}, {"from version 10", 10, 1, 10}, {"from version 11", 11, 0, 0}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { events, err := store.GetEvents("actor-123", tc.fromVersion) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != tc.expected { t.Errorf("expected %d events, got %d", tc.expected, len(events)) } // Verify all returned events have version >= fromVersion for _, event := range events { if event.Version < tc.fromVersion { t.Errorf("event version %d is less than fromVersion %d", event.Version, tc.fromVersion) } } // Verify minimum version if events exist if len(events) > 0 && events[0].Version != tc.minVersion { t.Errorf("first event version: got %d, want %d", events[0].Version, tc.minVersion) } }) } } func TestGetEvents_FromVersionZero(t *testing.T) { store := NewInMemoryEventStore() event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } // fromVersion 0 should return all events events, err := store.GetEvents("actor-123", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 1 { t.Errorf("expected 1 event with fromVersion 0, got %d", len(events)) } } func TestGetLatestVersion_ReturnsCorrectVersion(t *testing.T) { store := NewInMemoryEventStore() // Save events with strictly increasing versions versions := []int64{1, 2, 3, 4, 5} for i, version := range versions { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-123", Version: version, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } } latestVersion, err := store.GetLatestVersion("actor-123") if err != nil { t.Fatalf("GetLatestVersion failed: %v", err) } // Should return the highest version (5) if latestVersion != 5 { t.Errorf("expected latest version 5, got %d", latestVersion) } } func TestGetLatestVersion_SingleEvent(t *testing.T) { store := NewInMemoryEventStore() event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 42, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } latestVersion, err := store.GetLatestVersion("actor-123") if err != nil { t.Fatalf("GetLatestVersion failed: %v", err) } if latestVersion != 42 { t.Errorf("expected latest version 42, got %d", latestVersion) } } func TestGetLatestVersion_UpdatesAfterNewEvent(t *testing.T) { store := NewInMemoryEventStore() // Save first event event1 := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event1); err != nil { t.Fatalf("SaveEvent failed: %v", err) } version1, err := store.GetLatestVersion("actor-123") if err != nil { t.Fatalf("GetLatestVersion failed: %v", err) } if version1 != 1 { t.Errorf("expected version 1, got %d", version1) } // Save second event with higher version event2 := &aether.Event{ ID: "evt-2", EventType: "TestEvent", ActorID: "actor-123", Version: 10, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event2); err != nil { t.Fatalf("SaveEvent failed: %v", err) } version2, err := store.GetLatestVersion("actor-123") if err != nil { t.Fatalf("GetLatestVersion failed: %v", err) } if version2 != 10 { t.Errorf("expected version 10, got %d", version2) } } func TestGetEvents_NonExistentActor(t *testing.T) { store := NewInMemoryEventStore() // Save event for one actor event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } // Get events for non-existent actor events, err := store.GetEvents("non-existent-actor", 0) if err != nil { t.Fatalf("GetEvents should not error for non-existent actor: %v", err) } if events == nil { t.Error("GetEvents should return non-nil slice for non-existent actor") } if len(events) != 0 { t.Errorf("expected 0 events for non-existent actor, got %d", len(events)) } } func TestGetLatestVersion_NonExistentActor(t *testing.T) { store := NewInMemoryEventStore() // Get latest version for non-existent actor version, err := store.GetLatestVersion("non-existent-actor") if err != nil { t.Fatalf("GetLatestVersion should not error for non-existent actor: %v", err) } if version != 0 { t.Errorf("expected version 0 for non-existent actor, got %d", version) } } func TestGetLatestVersion_EmptyStore(t *testing.T) { store := NewInMemoryEventStore() version, err := store.GetLatestVersion("any-actor") if err != nil { t.Fatalf("GetLatestVersion should not error for empty store: %v", err) } if version != 0 { t.Errorf("expected version 0 for empty store, got %d", version) } } func TestGetEvents_EmptyStore(t *testing.T) { store := NewInMemoryEventStore() events, err := store.GetEvents("any-actor", 0) if err != nil { t.Fatalf("GetEvents should not error for empty store: %v", err) } if events == nil { t.Error("GetEvents should return non-nil slice for empty store") } if len(events) != 0 { t.Errorf("expected 0 events for empty store, got %d", len(events)) } } func TestConcurrentSaveEvent(t *testing.T) { store := NewInMemoryEventStore() numGoroutines := 100 eventsPerGoroutine := 10 var wg sync.WaitGroup wg.Add(numGoroutines) for g := 0; g < numGoroutines; g++ { go func(goroutineID int) { defer wg.Done() for i := 0; i < eventsPerGoroutine; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d-%d", goroutineID, i), EventType: "TestEvent", ActorID: fmt.Sprintf("actor-%d", goroutineID), Version: int64(i + 1), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Errorf("SaveEvent failed: %v", err) } } }(g) } wg.Wait() // Verify each actor has the correct number of events for g := 0; g < numGoroutines; g++ { actorID := fmt.Sprintf("actor-%d", g) events, err := store.GetEvents(actorID, 0) if err != nil { t.Errorf("GetEvents failed for %s: %v", actorID, err) continue } if len(events) != eventsPerGoroutine { t.Errorf("expected %d events for %s, got %d", eventsPerGoroutine, actorID, len(events)) } } } func TestConcurrentSaveAndGet(t *testing.T) { store := NewInMemoryEventStore() // Pre-populate with some events for i := 1; i <= 10; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-123", Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } } var wg sync.WaitGroup numReaders := 50 numWriters := 10 readsPerReader := 100 writesPerWriter := 10 // Start readers wg.Add(numReaders) for r := 0; r < numReaders; r++ { go func() { defer wg.Done() for i := 0; i < readsPerReader; i++ { events, err := store.GetEvents("actor-123", 0) if err != nil { t.Errorf("GetEvents failed: %v", err) } if len(events) < 10 { t.Errorf("expected at least 10 events, got %d", len(events)) } _, err = store.GetLatestVersion("actor-123") if err != nil { t.Errorf("GetLatestVersion failed: %v", err) } } }() } // Start writers - each writer gets their own actor to avoid version conflicts wg.Add(numWriters) for w := 0; w < numWriters; w++ { go func(writerID int) { defer wg.Done() actorID := fmt.Sprintf("writer-actor-%d", writerID) for i := 0; i < writesPerWriter; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-writer-%d-%d", writerID, i), EventType: "TestEvent", ActorID: actorID, Version: int64(i + 1), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Errorf("SaveEvent failed: %v", err) } } }(w) } wg.Wait() // Verify actor-123 still has its original events events, err := store.GetEvents("actor-123", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 10 { t.Errorf("expected 10 events for actor-123, got %d", len(events)) } // Verify each writer's actor has the correct events for w := 0; w < numWriters; w++ { actorID := fmt.Sprintf("writer-actor-%d", w) events, err := store.GetEvents(actorID, 0) if err != nil { t.Errorf("GetEvents failed for %s: %v", actorID, err) continue } if len(events) != writesPerWriter { t.Errorf("expected %d events for %s, got %d", writesPerWriter, actorID, len(events)) } } } func TestConcurrentGetLatestVersion(t *testing.T) { store := NewInMemoryEventStore() // Save initial event event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 100, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } var wg sync.WaitGroup numGoroutines := 100 wg.Add(numGoroutines) for i := 0; i < numGoroutines; i++ { go func() { defer wg.Done() for j := 0; j < 100; j++ { version, err := store.GetLatestVersion("actor-123") if err != nil { t.Errorf("GetLatestVersion failed: %v", err) } if version < 100 { t.Errorf("expected version >= 100, got %d", version) } } }() } wg.Wait() } func TestEventStoreInterface(t *testing.T) { // Verify InMemoryEventStore implements EventStore interface var _ aether.EventStore = (*InMemoryEventStore)(nil) } func TestSaveEvent_NilData(t *testing.T) { store := NewInMemoryEventStore() event := &aether.Event{ ID: "evt-nil", EventType: "NilDataEvent", ActorID: "actor-123", Version: 1, Data: nil, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed with nil data: %v", err) } events, err := store.GetEvents("actor-123", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if events[0].Data != nil { t.Errorf("expected nil Data, got %v", events[0].Data) } } func TestSaveEvent_EmptyData(t *testing.T) { store := NewInMemoryEventStore() event := &aether.Event{ ID: "evt-empty", EventType: "EmptyDataEvent", ActorID: "actor-123", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed with empty data: %v", err) } events, err := store.GetEvents("actor-123", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events[0].Data) != 0 { t.Errorf("expected empty Data map, got %v", events[0].Data) } } func TestGetEvents_VersionEdgeCases(t *testing.T) { store := NewInMemoryEventStore() // Save events with edge case versions (strictly increasing) versions := []int64{1, 100, 9223372036854775807} // one, 100, MaxInt64 for i, version := range versions { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-123", Version: version, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed for version %d: %v", version, err) } } // Test fromVersion 0 returns all events, err := store.GetEvents("actor-123", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 3 { t.Errorf("expected 3 events, got %d", len(events)) } // Test fromVersion MaxInt64 returns only that event events, err = store.GetEvents("actor-123", 9223372036854775807) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 1 { t.Errorf("expected 1 event, got %d", len(events)) } } func TestGetLatestVersion_VersionEdgeCases(t *testing.T) { store := NewInMemoryEventStore() // Save event with MaxInt64 version event := &aether.Event{ ID: "evt-max", EventType: "TestEvent", ActorID: "actor-123", Version: 9223372036854775807, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } latestVersion, err := store.GetLatestVersion("actor-123") if err != nil { t.Fatalf("GetLatestVersion failed: %v", err) } if latestVersion != 9223372036854775807 { t.Errorf("expected MaxInt64 version, got %d", latestVersion) } } func TestSaveEvent_SpecialActorIDs(t *testing.T) { store := NewInMemoryEventStore() specialIDs := []string{ "simple", "with-dashes", "with_underscores", "with.dots", "with:colons", "with/slashes", "user@example.com", "unicode-δΈ–η•Œ", "", } for _, actorID := range specialIDs { event := &aether.Event{ ID: fmt.Sprintf("evt-%s", actorID), EventType: "TestEvent", ActorID: actorID, Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Errorf("SaveEvent failed for actorID %q: %v", actorID, err) continue } events, err := store.GetEvents(actorID, 0) if err != nil { t.Errorf("GetEvents failed for actorID %q: %v", actorID, err) continue } if len(events) != 1 { t.Errorf("expected 1 event for actorID %q, got %d", actorID, len(events)) } } } // === Version Validation Tests === func TestSaveEvent_RejectsLowerVersion(t *testing.T) { store := NewInMemoryEventStore() // Save first event with version 5 event1 := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 5, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event1); err != nil { t.Fatalf("SaveEvent failed for first event: %v", err) } // Attempt to save event with lower version (should fail) event2 := &aether.Event{ ID: "evt-2", EventType: "TestEvent", ActorID: "actor-123", Version: 3, Data: map[string]interface{}{}, Timestamp: time.Now(), } err := store.SaveEvent(event2) if err == nil { t.Fatal("expected error when saving event with lower version, got nil") } // Verify it's a VersionConflictError if !errors.Is(err, aether.ErrVersionConflict) { t.Errorf("expected ErrVersionConflict, got %v", err) } var versionErr *aether.VersionConflictError if !errors.As(err, &versionErr) { t.Fatalf("expected VersionConflictError, got %T", err) } if versionErr.ActorID != "actor-123" { t.Errorf("ActorID mismatch: got %q, want %q", versionErr.ActorID, "actor-123") } if versionErr.CurrentVersion != 5 { t.Errorf("CurrentVersion mismatch: got %d, want %d", versionErr.CurrentVersion, 5) } if versionErr.AttemptedVersion != 3 { t.Errorf("AttemptedVersion mismatch: got %d, want %d", versionErr.AttemptedVersion, 3) } } func TestSaveEvent_RejectsEqualVersion(t *testing.T) { store := NewInMemoryEventStore() // Save first event with version 5 event1 := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-123", Version: 5, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event1); err != nil { t.Fatalf("SaveEvent failed for first event: %v", err) } // Attempt to save event with equal version (should fail) event2 := &aether.Event{ ID: "evt-2", EventType: "TestEvent", ActorID: "actor-123", Version: 5, Data: map[string]interface{}{}, Timestamp: time.Now(), } err := store.SaveEvent(event2) if err == nil { t.Fatal("expected error when saving event with equal version, got nil") } if !errors.Is(err, aether.ErrVersionConflict) { t.Errorf("expected ErrVersionConflict, got %v", err) } } func TestSaveEvent_RejectsZeroVersion(t *testing.T) { store := NewInMemoryEventStore() // Version 0 should be rejected (not strictly greater than initial 0) event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-new", Version: 0, Data: map[string]interface{}{}, Timestamp: time.Now(), } err := store.SaveEvent(event) if err == nil { t.Fatal("expected error when saving event with version 0, got nil") } if !errors.Is(err, aether.ErrVersionConflict) { t.Errorf("expected ErrVersionConflict, got %v", err) } } func TestSaveEvent_RejectsNegativeVersion(t *testing.T) { store := NewInMemoryEventStore() event := &aether.Event{ ID: "evt-neg", EventType: "TestEvent", ActorID: "actor-123", Version: -1, Data: map[string]interface{}{}, Timestamp: time.Now(), } err := store.SaveEvent(event) if err == nil { t.Fatal("expected error when saving event with negative version, got nil") } if !errors.Is(err, aether.ErrVersionConflict) { t.Errorf("expected ErrVersionConflict, got %v", err) } } func TestSaveEvent_ConcurrentWritesToSameActor(t *testing.T) { store := NewInMemoryEventStore() numGoroutines := 100 var successCount int64 var conflictCount int64 var wg sync.WaitGroup // All goroutines try to save version 1 wg.Add(numGoroutines) for i := 0; i < numGoroutines; i++ { go func(id int) { defer wg.Done() event := &aether.Event{ ID: fmt.Sprintf("evt-%d", id), EventType: "TestEvent", ActorID: "actor-contested", Version: 1, Data: map[string]interface{}{"goroutine": id}, Timestamp: time.Now(), } err := store.SaveEvent(event) if err == nil { atomic.AddInt64(&successCount, 1) } else if errors.Is(err, aether.ErrVersionConflict) { atomic.AddInt64(&conflictCount, 1) } else { t.Errorf("unexpected error: %v", err) } }(i) } wg.Wait() // Exactly one should succeed, rest should conflict if successCount != 1 { t.Errorf("expected exactly 1 success, got %d", successCount) } if conflictCount != int64(numGoroutines-1) { t.Errorf("expected %d conflicts, got %d", numGoroutines-1, conflictCount) } // Verify only one event was stored events, err := store.GetEvents("actor-contested", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(events) != 1 { t.Errorf("expected 1 event, got %d", len(events)) } } func BenchmarkSaveEvent(b *testing.B) { store := NewInMemoryEventStore() b.ResetTimer() for i := 0; i < b.N; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "BenchmarkEvent", ActorID: "actor-123", Version: int64(i + 1), Data: map[string]interface{}{"value": i}, Timestamp: time.Now(), } store.SaveEvent(event) } } func BenchmarkGetEvents(b *testing.B) { store := NewInMemoryEventStore() // Pre-populate with events for i := 0; i < 1000; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "BenchmarkEvent", ActorID: "actor-123", Version: int64(i + 1), Data: map[string]interface{}{}, Timestamp: time.Now(), } store.SaveEvent(event) } b.ResetTimer() for i := 0; i < b.N; i++ { store.GetEvents("actor-123", 0) } } func BenchmarkGetLatestVersion(b *testing.B) { store := NewInMemoryEventStore() // Pre-populate with events for i := 0; i < 1000; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "BenchmarkEvent", ActorID: "actor-123", Version: int64(i + 1), Data: map[string]interface{}{}, Timestamp: time.Now(), } store.SaveEvent(event) } b.ResetTimer() for i := 0; i < b.N; i++ { store.GetLatestVersion("actor-123") } } // === Snapshot Store Tests === func TestSaveSnapshot_PersistsCorrectly(t *testing.T) { store := NewInMemoryEventStore() snapshot := &aether.ActorSnapshot{ ActorID: "actor-123", Version: 10, State: map[string]interface{}{ "balance": 100.50, "status": "active", }, Timestamp: time.Date(2026, 1, 9, 12, 0, 0, 0, time.UTC), } err := store.SaveSnapshot(snapshot) if err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } // Verify snapshot was persisted by retrieving it retrieved, err := store.GetLatestSnapshot("actor-123") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if retrieved == nil { t.Fatal("expected snapshot to be persisted, got nil") } if retrieved.ActorID != snapshot.ActorID { t.Errorf("ActorID mismatch: got %q, want %q", retrieved.ActorID, snapshot.ActorID) } if retrieved.Version != snapshot.Version { t.Errorf("Version mismatch: got %d, want %d", retrieved.Version, snapshot.Version) } if retrieved.State["balance"] != snapshot.State["balance"] { t.Errorf("State.balance mismatch: got %v, want %v", retrieved.State["balance"], snapshot.State["balance"]) } if retrieved.State["status"] != snapshot.State["status"] { t.Errorf("State.status mismatch: got %v, want %v", retrieved.State["status"], snapshot.State["status"]) } if !retrieved.Timestamp.Equal(snapshot.Timestamp) { t.Errorf("Timestamp mismatch: got %v, want %v", retrieved.Timestamp, snapshot.Timestamp) } } func TestSaveSnapshot_NilSnapshot(t *testing.T) { store := NewInMemoryEventStore() err := store.SaveSnapshot(nil) if err == nil { t.Error("expected error when saving nil snapshot, got nil") } } func TestSaveSnapshot_MultipleSnapshots(t *testing.T) { store := NewInMemoryEventStore() // Save multiple snapshots for the same actor for i := 1; i <= 5; i++ { snapshot := &aether.ActorSnapshot{ ActorID: "actor-multi", Version: int64(i * 10), State: map[string]interface{}{ "iteration": i, }, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed for version %d: %v", i*10, err) } } // Verify all snapshots were saved by checking the latest retrieved, err := store.GetLatestSnapshot("actor-multi") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if retrieved.Version != 50 { t.Errorf("expected latest version 50, got %d", retrieved.Version) } } func TestGetLatestSnapshot_ReturnsMostRecent(t *testing.T) { store := NewInMemoryEventStore() // Save snapshots in non-sequential order to test version comparison versions := []int64{5, 15, 10, 25, 20} for _, v := range versions { snapshot := &aether.ActorSnapshot{ ActorID: "actor-latest", Version: v, State: map[string]interface{}{ "version": v, }, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed for version %d: %v", v, err) } } latest, err := store.GetLatestSnapshot("actor-latest") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if latest == nil { t.Fatal("expected snapshot, got nil") } if latest.Version != 25 { t.Errorf("expected latest version 25, got %d", latest.Version) } // Verify the state matches the snapshot with version 25 if latest.State["version"].(int64) != 25 { t.Errorf("expected state.version to be 25, got %v", latest.State["version"]) } } func TestGetLatestSnapshot_NoSnapshotExists(t *testing.T) { store := NewInMemoryEventStore() // Query for a non-existent actor snapshot, err := store.GetLatestSnapshot("non-existent-actor") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if snapshot != nil { t.Errorf("expected nil for non-existent actor, got %+v", snapshot) } } func TestGetLatestSnapshot_EmptyActorID(t *testing.T) { store := NewInMemoryEventStore() // Save a snapshot with empty actor ID snapshot := &aether.ActorSnapshot{ ActorID: "", Version: 1, State: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } // Retrieve with empty actor ID retrieved, err := store.GetLatestSnapshot("") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if retrieved == nil { t.Fatal("expected snapshot with empty actorID, got nil") } } func TestSnapshotVersioning_RespectedAcrossActors(t *testing.T) { store := NewInMemoryEventStore() // Save snapshots for different actors actors := []string{"actor-a", "actor-b", "actor-c"} for i, actorID := range actors { snapshot := &aether.ActorSnapshot{ ActorID: actorID, Version: int64((i + 1) * 100), // Different versions per actor State: map[string]interface{}{ "actor": actorID, }, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed for %s: %v", actorID, err) } } // Verify each actor has their own snapshot with correct version for i, actorID := range actors { snapshot, err := store.GetLatestSnapshot(actorID) if err != nil { t.Fatalf("GetLatestSnapshot failed for %s: %v", actorID, err) } expectedVersion := int64((i + 1) * 100) if snapshot.Version != expectedVersion { t.Errorf("actor %s: expected version %d, got %d", actorID, expectedVersion, snapshot.Version) } } } func TestSnapshotVersioning_LowerVersionAfterHigher(t *testing.T) { store := NewInMemoryEventStore() // Save a high version first highSnapshot := &aether.ActorSnapshot{ ActorID: "actor-order", Version: 100, State: map[string]interface{}{ "marker": "high", }, Timestamp: time.Now(), } if err := store.SaveSnapshot(highSnapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } // Save a lower version after lowSnapshot := &aether.ActorSnapshot{ ActorID: "actor-order", Version: 50, State: map[string]interface{}{ "marker": "low", }, Timestamp: time.Now(), } if err := store.SaveSnapshot(lowSnapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } // GetLatestSnapshot should return the higher version (100), not the most recently saved latest, err := store.GetLatestSnapshot("actor-order") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if latest.Version != 100 { t.Errorf("expected version 100, got %d", latest.Version) } if latest.State["marker"] != "high" { t.Errorf("expected marker 'high', got %v", latest.State["marker"]) } } func TestSnapshotDataIntegrity_ComplexState(t *testing.T) { store := NewInMemoryEventStore() complexState := map[string]interface{}{ "string": "hello", "integer": 42, "float": 3.14159, "boolean": true, "null": nil, "array": []interface{}{"a", "b", "c"}, "nested": map[string]interface{}{ "level1": map[string]interface{}{ "level2": "deep value", }, }, } snapshot := &aether.ActorSnapshot{ ActorID: "actor-complex", Version: 1, State: complexState, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-complex") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } // Verify all fields if retrieved.State["string"] != "hello" { t.Errorf("string mismatch: got %v", retrieved.State["string"]) } if retrieved.State["integer"] != 42 { t.Errorf("integer mismatch: got %v", retrieved.State["integer"]) } if retrieved.State["float"] != 3.14159 { t.Errorf("float mismatch: got %v", retrieved.State["float"]) } if retrieved.State["boolean"] != true { t.Errorf("boolean mismatch: got %v", retrieved.State["boolean"]) } if retrieved.State["null"] != nil { t.Errorf("null mismatch: got %v", retrieved.State["null"]) } // Verify array arr, ok := retrieved.State["array"].([]interface{}) if !ok { t.Fatal("array is not []interface{}") } if len(arr) != 3 || arr[0] != "a" || arr[1] != "b" || arr[2] != "c" { t.Errorf("array mismatch: got %v", arr) } // Verify nested structure nested, ok := retrieved.State["nested"].(map[string]interface{}) if !ok { t.Fatal("nested is not map[string]interface{}") } level1, ok := nested["level1"].(map[string]interface{}) if !ok { t.Fatal("level1 is not map[string]interface{}") } if level1["level2"] != "deep value" { t.Errorf("nested value mismatch: got %v", level1["level2"]) } } func TestSnapshotDataIntegrity_SpecialCharacters(t *testing.T) { store := NewInMemoryEventStore() specialState := map[string]interface{}{ "unicode": "Hello, δΈ–η•Œ!", "emoji": "πŸ˜€πŸš€", "newlines": "line1\nline2\r\nline3", "tabs": "col1\tcol2", "quotes": `"double" and 'single'`, "backslash": `path\to\file`, } snapshot := &aether.ActorSnapshot{ ActorID: "actor-special", Version: 1, State: specialState, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-special") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } for key, expected := range specialState { if retrieved.State[key] != expected { t.Errorf("State[%q] mismatch: got %q, want %q", key, retrieved.State[key], expected) } } } func TestSnapshotDataIntegrity_EmptyState(t *testing.T) { store := NewInMemoryEventStore() snapshot := &aether.ActorSnapshot{ ActorID: "actor-empty", Version: 1, State: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-empty") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if len(retrieved.State) != 0 { t.Errorf("expected empty state, got %v", retrieved.State) } } func TestSnapshotDataIntegrity_NilState(t *testing.T) { store := NewInMemoryEventStore() snapshot := &aether.ActorSnapshot{ ActorID: "actor-nil", Version: 1, State: nil, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-nil") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if retrieved.State != nil { t.Errorf("expected nil state, got %v", retrieved.State) } } func TestSnapshotDataIntegrity_LargeState(t *testing.T) { store := NewInMemoryEventStore() // Create a large state with many entries using unique keys largeState := make(map[string]interface{}) for i := 0; i < 1000; i++ { largeState[fmt.Sprintf("key-%d", i)] = i } snapshot := &aether.ActorSnapshot{ ActorID: "actor-large", Version: 1, State: largeState, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-large") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if len(retrieved.State) != len(largeState) { t.Errorf("state size mismatch: got %d, want %d", len(retrieved.State), len(largeState)) } } func TestSnapshotDataIntegrity_TimestampPreserved(t *testing.T) { store := NewInMemoryEventStore() // Test various timestamps timestamps := []time.Time{ time.Date(2026, 1, 9, 12, 0, 0, 0, time.UTC), time.Date(2020, 6, 15, 23, 59, 59, 999999999, time.UTC), time.Time{}, // Zero time } for i, ts := range timestamps { actorID := fmt.Sprintf("actor-ts-%d", i) snapshot := &aether.ActorSnapshot{ ActorID: actorID, Version: 1, State: map[string]interface{}{}, Timestamp: ts, } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot(actorID) if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if !retrieved.Timestamp.Equal(ts) { t.Errorf("timestamp %d mismatch: got %v, want %v", i, retrieved.Timestamp, ts) } } } func TestSnapshotVersioning_ZeroVersion(t *testing.T) { store := NewInMemoryEventStore() snapshot := &aether.ActorSnapshot{ ActorID: "actor-zero-version", Version: 0, State: map[string]interface{}{"initial": true}, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-zero-version") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if retrieved.Version != 0 { t.Errorf("expected version 0, got %d", retrieved.Version) } } func TestSnapshotVersioning_LargeVersion(t *testing.T) { store := NewInMemoryEventStore() largeVersion := int64(9223372036854775807) // MaxInt64 snapshot := &aether.ActorSnapshot{ ActorID: "actor-large-version", Version: largeVersion, State: map[string]interface{}{"maxed": true}, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } retrieved, err := store.GetLatestSnapshot("actor-large-version") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if retrieved.Version != largeVersion { t.Errorf("expected version %d, got %d", largeVersion, retrieved.Version) } } func TestSnapshotStore_ImplementsInterface(t *testing.T) { // Verify InMemoryEventStore implements SnapshotStore interface var _ aether.SnapshotStore = (*InMemoryEventStore)(nil) } func TestConcurrentSaveSnapshot(t *testing.T) { store := NewInMemoryEventStore() numGoroutines := 100 snapshotsPerGoroutine := 10 var wg sync.WaitGroup wg.Add(numGoroutines) for g := 0; g < numGoroutines; g++ { go func(goroutineID int) { defer wg.Done() for i := 0; i < snapshotsPerGoroutine; i++ { snapshot := &aether.ActorSnapshot{ ActorID: fmt.Sprintf("actor-%d", goroutineID), Version: int64(i + 1), State: map[string]interface{}{ "goroutine": goroutineID, "iteration": i, }, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Errorf("SaveSnapshot failed: %v", err) } } }(g) } wg.Wait() // Verify each actor has snapshots for g := 0; g < numGoroutines; g++ { actorID := fmt.Sprintf("actor-%d", g) snapshot, err := store.GetLatestSnapshot(actorID) if err != nil { t.Errorf("GetLatestSnapshot failed for %s: %v", actorID, err) continue } if snapshot == nil { t.Errorf("expected snapshot for %s, got nil", actorID) continue } if snapshot.Version != int64(snapshotsPerGoroutine) { t.Errorf("expected latest version %d for %s, got %d", snapshotsPerGoroutine, actorID, snapshot.Version) } } } func TestConcurrentSaveAndGetSnapshot(t *testing.T) { store := NewInMemoryEventStore() // Pre-populate with initial snapshot initialSnapshot := &aether.ActorSnapshot{ ActorID: "actor-123", Version: 1, State: map[string]interface{}{ "initial": true, }, Timestamp: time.Now(), } if err := store.SaveSnapshot(initialSnapshot); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } var wg sync.WaitGroup numReaders := 50 numWriters := 10 readsPerReader := 100 writesPerWriter := 10 // Start readers wg.Add(numReaders) for r := 0; r < numReaders; r++ { go func() { defer wg.Done() for i := 0; i < readsPerReader; i++ { snapshot, err := store.GetLatestSnapshot("actor-123") if err != nil { t.Errorf("GetLatestSnapshot failed: %v", err) } if snapshot == nil { t.Error("expected snapshot, got nil") } } }() } // Start writers wg.Add(numWriters) for w := 0; w < numWriters; w++ { go func(writerID int) { defer wg.Done() for i := 0; i < writesPerWriter; i++ { snapshot := &aether.ActorSnapshot{ ActorID: "actor-123", Version: int64(100 + writerID*writesPerWriter + i), State: map[string]interface{}{ "writer": writerID, "index": i, }, Timestamp: time.Now(), } if err := store.SaveSnapshot(snapshot); err != nil { t.Errorf("SaveSnapshot failed: %v", err) } } }(w) } wg.Wait() // Verify final state - should have the highest version snapshot, err := store.GetLatestSnapshot("actor-123") if err != nil { t.Fatalf("GetLatestSnapshot failed: %v", err) } if snapshot == nil { t.Fatal("expected snapshot, got nil") } // The highest version should be around 100 + (numWriters-1)*writesPerWriter + (writesPerWriter-1) // which is 100 + 9*10 + 9 = 199 expectedMaxVersion := int64(100 + (numWriters-1)*writesPerWriter + (writesPerWriter - 1)) if snapshot.Version != expectedMaxVersion { t.Errorf("expected latest version %d, got %d", expectedMaxVersion, snapshot.Version) } }