Add event metadata support for distributed tracing and auditing
All checks were successful
CI / build (pull_request) Successful in 15s

- Add Metadata field (map[string]string) to Event struct with omitempty
- Add helper methods for common metadata: SetCorrelationID/GetCorrelationID,
  SetCausationID/GetCausationID, SetUserID/GetUserID, SetTraceID/GetTraceID,
  SetSpanID/GetSpanID
- Add WithMetadataFrom helper for copying metadata between events
- Add metadata key constants for standard fields
- Add comprehensive unit tests for metadata serialization and helpers
- Add store tests verifying metadata persistence

Closes #7

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 17:53:05 +01:00
parent a269da4520
commit 97ff1c3346
3 changed files with 781 additions and 0 deletions

View File

@@ -1488,3 +1488,222 @@ func TestConcurrentSaveAndGetSnapshot(t *testing.T) {
t.Errorf("expected latest version %d, got %d", expectedMaxVersion, snapshot.Version)
}
}
// === Event Metadata Persistence Tests ===
func TestSaveEvent_WithMetadata(t *testing.T) {
store := NewInMemoryEventStore()
event := &aether.Event{
ID: "evt-meta",
EventType: "OrderPlaced",
ActorID: "order-456",
Version: 1,
Data: map[string]interface{}{
"total": 100.50,
},
Metadata: map[string]string{
"correlationId": "corr-123",
"causationId": "cause-456",
"userId": "user-789",
"traceId": "trace-abc",
"spanId": "span-def",
},
Timestamp: time.Now(),
}
err := store.SaveEvent(event)
if err != nil {
t.Fatalf("SaveEvent failed: %v", err)
}
// Retrieve and verify metadata is 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))
}
retrieved := events[0]
if retrieved.Metadata == nil {
t.Fatal("expected Metadata to be persisted")
}
if retrieved.Metadata["correlationId"] != "corr-123" {
t.Errorf("correlationId mismatch: got %q, want %q", retrieved.Metadata["correlationId"], "corr-123")
}
if retrieved.Metadata["causationId"] != "cause-456" {
t.Errorf("causationId mismatch: got %q, want %q", retrieved.Metadata["causationId"], "cause-456")
}
if retrieved.Metadata["userId"] != "user-789" {
t.Errorf("userId mismatch: got %q, want %q", retrieved.Metadata["userId"], "user-789")
}
if retrieved.Metadata["traceId"] != "trace-abc" {
t.Errorf("traceId mismatch: got %q, want %q", retrieved.Metadata["traceId"], "trace-abc")
}
if retrieved.Metadata["spanId"] != "span-def" {
t.Errorf("spanId mismatch: got %q, want %q", retrieved.Metadata["spanId"], "span-def")
}
}
func TestSaveEvent_WithNilMetadata(t *testing.T) {
store := NewInMemoryEventStore()
event := &aether.Event{
ID: "evt-nil-meta",
EventType: "OrderPlaced",
ActorID: "order-456",
Version: 1,
Data: map[string]interface{}{},
Metadata: nil,
Timestamp: time.Now(),
}
err := store.SaveEvent(event)
if err != nil {
t.Fatalf("SaveEvent failed: %v", err)
}
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))
}
// Nil metadata should remain nil
if events[0].Metadata != nil {
t.Errorf("expected nil Metadata, got %v", events[0].Metadata)
}
}
func TestSaveEvent_WithEmptyMetadata(t *testing.T) {
store := NewInMemoryEventStore()
event := &aether.Event{
ID: "evt-empty-meta",
EventType: "OrderPlaced",
ActorID: "order-456",
Version: 1,
Data: map[string]interface{}{},
Metadata: map[string]string{},
Timestamp: time.Now(),
}
err := store.SaveEvent(event)
if err != nil {
t.Fatalf("SaveEvent failed: %v", err)
}
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))
}
// Empty metadata should be preserved (as empty map)
if events[0].Metadata == nil {
t.Error("expected empty Metadata map to be preserved, got nil")
}
if len(events[0].Metadata) != 0 {
t.Errorf("expected empty Metadata, got %d entries", len(events[0].Metadata))
}
}
func TestSaveEvent_MetadataWithHelpers(t *testing.T) {
store := NewInMemoryEventStore()
event := &aether.Event{
ID: "evt-helpers",
EventType: "OrderPlaced",
ActorID: "order-456",
Version: 1,
Data: map[string]interface{}{},
Timestamp: time.Now(),
}
// Use helper methods to set metadata
event.SetCorrelationID("corr-helper")
event.SetCausationID("cause-helper")
event.SetUserID("user-helper")
event.SetTraceID("trace-helper")
event.SetSpanID("span-helper")
err := store.SaveEvent(event)
if err != nil {
t.Fatalf("SaveEvent failed: %v", err)
}
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))
}
retrieved := events[0]
if retrieved.GetCorrelationID() != "corr-helper" {
t.Errorf("GetCorrelationID mismatch: got %q", retrieved.GetCorrelationID())
}
if retrieved.GetCausationID() != "cause-helper" {
t.Errorf("GetCausationID mismatch: got %q", retrieved.GetCausationID())
}
if retrieved.GetUserID() != "user-helper" {
t.Errorf("GetUserID mismatch: got %q", retrieved.GetUserID())
}
if retrieved.GetTraceID() != "trace-helper" {
t.Errorf("GetTraceID mismatch: got %q", retrieved.GetTraceID())
}
if retrieved.GetSpanID() != "span-helper" {
t.Errorf("GetSpanID mismatch: got %q", retrieved.GetSpanID())
}
}
func TestSaveEvent_MetadataPreservedAcrossMultipleEvents(t *testing.T) {
store := NewInMemoryEventStore()
// Save multiple events with different metadata
for i := 1; i <= 3; i++ {
event := &aether.Event{
ID: fmt.Sprintf("evt-%d", i),
EventType: "OrderUpdated",
ActorID: "order-456",
Version: int64(i),
Data: map[string]interface{}{},
Metadata: map[string]string{
"correlationId": fmt.Sprintf("corr-%d", i),
"eventIndex": fmt.Sprintf("%d", i),
},
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) != 3 {
t.Fatalf("expected 3 events, got %d", len(events))
}
// Verify each event has its own metadata
for i, event := range events {
expectedCorr := fmt.Sprintf("corr-%d", i+1)
expectedIdx := fmt.Sprintf("%d", i+1)
if event.Metadata["correlationId"] != expectedCorr {
t.Errorf("event %d correlationId mismatch: got %q, want %q", i+1, event.Metadata["correlationId"], expectedCorr)
}
if event.Metadata["eventIndex"] != expectedIdx {
t.Errorf("event %d eventIndex mismatch: got %q, want %q", i+1, event.Metadata["eventIndex"], expectedIdx)
}
}
}