package store import ( "fmt" "sync" "testing" "time" "git.flowmade.one/flowmade-one/aether" ) // MockEventBroadcaster captures published events for testing type MockEventBroadcaster struct { mu sync.RWMutex events []*aether.Event namespaces map[string]int } func NewMockEventBroadcaster() *MockEventBroadcaster { return &MockEventBroadcaster{ events: make([]*aether.Event, 0), namespaces: make(map[string]int), } } func (m *MockEventBroadcaster) Subscribe(namespacePattern string) <-chan *aether.Event { return make(chan *aether.Event) } func (m *MockEventBroadcaster) SubscribeWithFilter(namespacePattern string, filter *aether.SubscriptionFilter) <-chan *aether.Event { return make(chan *aether.Event) } func (m *MockEventBroadcaster) Unsubscribe(namespacePattern string, ch <-chan *aether.Event) {} func (m *MockEventBroadcaster) Publish(namespaceID string, event *aether.Event) { m.mu.Lock() defer m.mu.Unlock() m.events = append(m.events, event) m.namespaces[namespaceID]++ } func (m *MockEventBroadcaster) Stop() {} func (m *MockEventBroadcaster) SubscriberCount(namespaceID string) int { return 0 } func (m *MockEventBroadcaster) GetPublishedEvents() []*aether.Event { m.mu.RLock() defer m.mu.RUnlock() events := make([]*aether.Event, len(m.events)) copy(events, m.events) return events } // === InMemoryEventStore EventStored Tests === func TestEventStored_PublishedOnSaveSuccess(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) 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 EventStored was published published := mockBus.GetPublishedEvents() if len(published) != 1 { t.Fatalf("expected 1 published event, got %d", len(published)) } storedEvent := published[0] if storedEvent.EventType != "EventStored" { t.Errorf("expected EventType 'EventStored', got %q", storedEvent.EventType) } if storedEvent.ActorID != "order-456" { t.Errorf("expected ActorID 'order-456', got %q", storedEvent.ActorID) } if storedEvent.Data["eventId"] != "evt-123" { t.Errorf("expected eventId 'evt-123', got %v", storedEvent.Data["eventId"]) } if storedEvent.Data["version"] != int64(1) { t.Errorf("expected version 1, got %v", storedEvent.Data["version"]) } } func TestEventStored_NotPublishedOnVersionConflict(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) // Save first event event1 := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-1", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event1); err != nil { t.Fatalf("First SaveEvent failed: %v", err) } // Try to save event with same version (conflict) event2 := &aether.Event{ ID: "evt-2", EventType: "TestEvent", ActorID: "actor-1", Version: 1, // Same version - should conflict Data: map[string]interface{}{}, Timestamp: time.Now(), } err := store.SaveEvent(event2) if err == nil { t.Fatal("expected VersionConflictError, got nil") } // Verify only 1 EventStored was published (from first event) published := mockBus.GetPublishedEvents() if len(published) != 1 { t.Fatalf("expected 1 published event after conflict, got %d", len(published)) } } func TestEventStored_MultipleEventsPublished(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) // Save 5 events for i := 1; i <= 5; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-1", Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent %d failed: %v", i, err) } } // Verify 5 EventStored events were published published := mockBus.GetPublishedEvents() if len(published) != 5 { t.Fatalf("expected 5 published events, got %d", len(published)) } // Verify each has correct data for i := 0; i < 5; i++ { if published[i].Data["version"] != int64(i+1) { t.Errorf("event %d: expected version %d, got %v", i, i+1, published[i].Data["version"]) } } } func TestEventStored_NotPublishedWithoutEventBus(t *testing.T) { store := NewInMemoryEventStore() // Don't set event bus event := &aether.Event{ ID: "evt-123", EventType: "OrderPlaced", ActorID: "order-456", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } // Should succeed without publishing (no-op) err := store.SaveEvent(event) if err != nil { t.Fatalf("SaveEvent failed: %v", err) } // Event should be persisted normally retrieved, err := store.GetEvents("order-456", 0) if err != nil { t.Fatalf("GetEvents failed: %v", err) } if len(retrieved) != 1 { t.Errorf("expected 1 event, got %d", len(retrieved)) } } func TestEventStored_ContainsRequiredFields(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) event := &aether.Event{ ID: "evt-abc", EventType: "TestEvent", ActorID: "actor-xyz", Version: 42, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } published := mockBus.GetPublishedEvents() if len(published) != 1 { t.Fatalf("expected 1 event, got %d", len(published)) } storedEvent := published[0] // Verify required fields if storedEvent.Data["eventId"] != "evt-abc" { t.Error("missing or incorrect eventId") } if storedEvent.Data["actorId"] != "actor-xyz" { t.Error("missing or incorrect actorId") } if storedEvent.Data["version"] != int64(42) { t.Error("missing or incorrect version") } if _, hasTimestamp := storedEvent.Data["timestamp"]; !hasTimestamp { t.Error("missing timestamp") } } func TestEventStored_PublishedToCorrectNamespace(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-1", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } // Verify published to __internal__ namespace namespaces := mockBus.namespaces if count, ok := namespaces["__internal__"]; !ok || count != 1 { t.Errorf("expected 1 event published to __internal__, got %v", namespaces) } } func TestEventStored_WithMetricsRecording(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() mockMetrics := aether.NewMetricsCollector() store.WithEventBus(mockBus) store.WithMetrics(mockMetrics) event := &aether.Event{ ID: "evt-1", EventType: "TestEvent", ActorID: "actor-1", Version: 1, Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent failed: %v", err) } // Verify metrics were recorded published := mockMetrics.EventsPublished("__internal__") if published != 1 { t.Errorf("expected 1 published metric, got %d", published) } } func TestEventStored_ConcurrentPublishing(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) numGoroutines := 10 eventsPerGoroutine := 5 var wg sync.WaitGroup for g := 0; g < numGoroutines; g++ { wg.Add(1) go func(goroutineID int) { defer wg.Done() for i := 0; i < eventsPerGoroutine; i++ { version := int64(goroutineID*eventsPerGoroutine + i + 1) event := &aether.Event{ ID: fmt.Sprintf("evt-%d-%d", goroutineID, i), EventType: "TestEvent", ActorID: fmt.Sprintf("actor-%d", goroutineID), Version: version, Data: map[string]interface{}{}, Timestamp: time.Now(), } _ = store.SaveEvent(event) // Ignore errors (some may conflict) } }(g) } wg.Wait() // Verify EventStored events were published for successful saves published := mockBus.GetPublishedEvents() if len(published) != numGoroutines*eventsPerGoroutine { t.Logf("Note: got %d published events (some saves may have conflicted)", len(published)) } if len(published) == 0 { t.Fatal("expected at least some published events") } } func TestEventStored_OrderPreserved(t *testing.T) { store := NewInMemoryEventStore() mockBus := NewMockEventBroadcaster() store.WithEventBus(mockBus) // Save 3 events in order for i := 1; i <= 3; i++ { event := &aether.Event{ ID: fmt.Sprintf("evt-%d", i), EventType: "TestEvent", ActorID: "actor-1", Version: int64(i), Data: map[string]interface{}{}, Timestamp: time.Now(), } if err := store.SaveEvent(event); err != nil { t.Fatalf("SaveEvent %d failed: %v", i, err) } } published := mockBus.GetPublishedEvents() // Verify order is preserved for i := 0; i < 3; i++ { if published[i].Data["eventId"] != fmt.Sprintf("evt-%d", i+1) { t.Errorf("event %d: expected evt-%d, got %v", i, i+1, published[i].Data["eventId"]) } } }