Add event metadata support for distributed tracing and auditing
All checks were successful
CI / build (push) Successful in 15s
All checks were successful
CI / build (push) 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 was merged in pull request #33.
This commit is contained in:
94
event.go
94
event.go
@@ -36,9 +36,103 @@ type Event struct {
|
||||
CommandID string `json:"commandId,omitempty"` // Correlation ID for command that triggered this event
|
||||
Version int64 `json:"version"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"` // Optional metadata for tracing and auditing
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Common metadata keys for distributed tracing and auditing
|
||||
const (
|
||||
// MetadataKeyCorrelationID identifies related events across services
|
||||
MetadataKeyCorrelationID = "correlationId"
|
||||
// MetadataKeyCausationID identifies the event that caused this event
|
||||
MetadataKeyCausationID = "causationId"
|
||||
// MetadataKeyUserID identifies the user who triggered this event
|
||||
MetadataKeyUserID = "userId"
|
||||
// MetadataKeyTraceID for distributed tracing integration (e.g., OpenTelemetry)
|
||||
MetadataKeyTraceID = "traceId"
|
||||
// MetadataKeySpanID for distributed tracing integration
|
||||
MetadataKeySpanID = "spanId"
|
||||
)
|
||||
|
||||
// SetMetadata sets a metadata key-value pair, initializing the map if needed
|
||||
func (e *Event) SetMetadata(key, value string) {
|
||||
if e.Metadata == nil {
|
||||
e.Metadata = make(map[string]string)
|
||||
}
|
||||
e.Metadata[key] = value
|
||||
}
|
||||
|
||||
// GetMetadata returns the value for a metadata key, or empty string if not found
|
||||
func (e *Event) GetMetadata(key string) string {
|
||||
if e.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Metadata[key]
|
||||
}
|
||||
|
||||
// SetCorrelationID sets the correlation ID metadata
|
||||
func (e *Event) SetCorrelationID(correlationID string) {
|
||||
e.SetMetadata(MetadataKeyCorrelationID, correlationID)
|
||||
}
|
||||
|
||||
// GetCorrelationID returns the correlation ID metadata
|
||||
func (e *Event) GetCorrelationID() string {
|
||||
return e.GetMetadata(MetadataKeyCorrelationID)
|
||||
}
|
||||
|
||||
// SetCausationID sets the causation ID metadata
|
||||
func (e *Event) SetCausationID(causationID string) {
|
||||
e.SetMetadata(MetadataKeyCausationID, causationID)
|
||||
}
|
||||
|
||||
// GetCausationID returns the causation ID metadata
|
||||
func (e *Event) GetCausationID() string {
|
||||
return e.GetMetadata(MetadataKeyCausationID)
|
||||
}
|
||||
|
||||
// SetUserID sets the user ID metadata
|
||||
func (e *Event) SetUserID(userID string) {
|
||||
e.SetMetadata(MetadataKeyUserID, userID)
|
||||
}
|
||||
|
||||
// GetUserID returns the user ID metadata
|
||||
func (e *Event) GetUserID() string {
|
||||
return e.GetMetadata(MetadataKeyUserID)
|
||||
}
|
||||
|
||||
// SetTraceID sets the trace ID metadata for distributed tracing
|
||||
func (e *Event) SetTraceID(traceID string) {
|
||||
e.SetMetadata(MetadataKeyTraceID, traceID)
|
||||
}
|
||||
|
||||
// GetTraceID returns the trace ID metadata
|
||||
func (e *Event) GetTraceID() string {
|
||||
return e.GetMetadata(MetadataKeyTraceID)
|
||||
}
|
||||
|
||||
// SetSpanID sets the span ID metadata for distributed tracing
|
||||
func (e *Event) SetSpanID(spanID string) {
|
||||
e.SetMetadata(MetadataKeySpanID, spanID)
|
||||
}
|
||||
|
||||
// GetSpanID returns the span ID metadata
|
||||
func (e *Event) GetSpanID() string {
|
||||
return e.GetMetadata(MetadataKeySpanID)
|
||||
}
|
||||
|
||||
// WithMetadataFrom copies metadata from another event (useful for event chaining)
|
||||
func (e *Event) WithMetadataFrom(source *Event) {
|
||||
if source == nil || source.Metadata == nil {
|
||||
return
|
||||
}
|
||||
if e.Metadata == nil {
|
||||
e.Metadata = make(map[string]string)
|
||||
}
|
||||
for k, v := range source.Metadata {
|
||||
e.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// ActorSnapshot represents a point-in-time state snapshot
|
||||
type ActorSnapshot struct {
|
||||
ActorID string `json:"actorId"`
|
||||
|
||||
468
event_test.go
468
event_test.go
@@ -740,3 +740,471 @@ func TestActorSnapshot_VersionEdgeCases(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for Event Metadata support
|
||||
|
||||
func TestEvent_MetadataJSONSerialization(t *testing.T) {
|
||||
ts := time.Date(2026, 1, 9, 12, 0, 0, 0, time.UTC)
|
||||
event := &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",
|
||||
},
|
||||
Timestamp: ts,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal Event with metadata: %v", err)
|
||||
}
|
||||
|
||||
var decoded Event
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("failed to unmarshal Event with metadata: %v", err)
|
||||
}
|
||||
|
||||
if decoded.Metadata == nil {
|
||||
t.Fatal("expected Metadata to be present after unmarshal")
|
||||
}
|
||||
if decoded.Metadata["correlationId"] != "corr-123" {
|
||||
t.Errorf("correlationId mismatch: got %q, want %q", decoded.Metadata["correlationId"], "corr-123")
|
||||
}
|
||||
if decoded.Metadata["causationId"] != "cause-456" {
|
||||
t.Errorf("causationId mismatch: got %q, want %q", decoded.Metadata["causationId"], "cause-456")
|
||||
}
|
||||
if decoded.Metadata["userId"] != "user-789" {
|
||||
t.Errorf("userId mismatch: got %q, want %q", decoded.Metadata["userId"], "user-789")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_MetadataOmitEmpty(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-no-meta",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Data: map[string]interface{}{},
|
||||
Metadata: nil,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal Event: %v", err)
|
||||
}
|
||||
|
||||
if strings.Contains(string(data), `"metadata"`) {
|
||||
t.Error("expected metadata to be omitted when nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_EmptyMetadata(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-empty-meta",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Data: map[string]interface{}{},
|
||||
Metadata: map[string]string{},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal Event with empty metadata: %v", err)
|
||||
}
|
||||
|
||||
var decoded Event
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("failed to unmarshal Event with empty metadata: %v", err)
|
||||
}
|
||||
|
||||
// Empty map should be omitted with omitempty
|
||||
if strings.Contains(string(data), `"metadata"`) {
|
||||
t.Error("expected empty metadata to be omitted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_SetMetadata(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-set-meta",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
// Metadata should be nil initially
|
||||
if event.Metadata != nil {
|
||||
t.Error("expected Metadata to be nil initially")
|
||||
}
|
||||
|
||||
// SetMetadata should initialize the map
|
||||
event.SetMetadata("key1", "value1")
|
||||
if event.Metadata == nil {
|
||||
t.Fatal("expected Metadata to be initialized after SetMetadata")
|
||||
}
|
||||
if event.Metadata["key1"] != "value1" {
|
||||
t.Errorf("key1 mismatch: got %q, want %q", event.Metadata["key1"], "value1")
|
||||
}
|
||||
|
||||
// SetMetadata should work with existing map
|
||||
event.SetMetadata("key2", "value2")
|
||||
if event.Metadata["key2"] != "value2" {
|
||||
t.Errorf("key2 mismatch: got %q, want %q", event.Metadata["key2"], "value2")
|
||||
}
|
||||
if event.Metadata["key1"] != "value1" {
|
||||
t.Errorf("key1 was overwritten: got %q", event.Metadata["key1"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_GetMetadata(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-get-meta",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
// GetMetadata on nil map should return empty string
|
||||
if got := event.GetMetadata("nonexistent"); got != "" {
|
||||
t.Errorf("expected empty string for nil Metadata, got %q", got)
|
||||
}
|
||||
|
||||
// Initialize metadata and test
|
||||
event.Metadata = map[string]string{"key": "value"}
|
||||
if got := event.GetMetadata("key"); got != "value" {
|
||||
t.Errorf("GetMetadata mismatch: got %q, want %q", got, "value")
|
||||
}
|
||||
|
||||
// Non-existent key should return empty string
|
||||
if got := event.GetMetadata("nonexistent"); got != "" {
|
||||
t.Errorf("expected empty string for nonexistent key, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_CorrelationIDHelpers(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-corr",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
// GetCorrelationID on nil metadata
|
||||
if got := event.GetCorrelationID(); got != "" {
|
||||
t.Errorf("expected empty correlation ID, got %q", got)
|
||||
}
|
||||
|
||||
// SetCorrelationID
|
||||
event.SetCorrelationID("corr-abc-123")
|
||||
if got := event.GetCorrelationID(); got != "corr-abc-123" {
|
||||
t.Errorf("GetCorrelationID mismatch: got %q, want %q", got, "corr-abc-123")
|
||||
}
|
||||
|
||||
// Verify it uses the correct metadata key
|
||||
if event.Metadata[MetadataKeyCorrelationID] != "corr-abc-123" {
|
||||
t.Errorf("expected correlationId key to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_CausationIDHelpers(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-cause",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
// GetCausationID on nil metadata
|
||||
if got := event.GetCausationID(); got != "" {
|
||||
t.Errorf("expected empty causation ID, got %q", got)
|
||||
}
|
||||
|
||||
// SetCausationID
|
||||
event.SetCausationID("cause-xyz-789")
|
||||
if got := event.GetCausationID(); got != "cause-xyz-789" {
|
||||
t.Errorf("GetCausationID mismatch: got %q, want %q", got, "cause-xyz-789")
|
||||
}
|
||||
|
||||
// Verify it uses the correct metadata key
|
||||
if event.Metadata[MetadataKeyCausationID] != "cause-xyz-789" {
|
||||
t.Errorf("expected causationId key to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_UserIDHelpers(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-user",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
event.SetUserID("user-john-doe")
|
||||
if got := event.GetUserID(); got != "user-john-doe" {
|
||||
t.Errorf("GetUserID mismatch: got %q, want %q", got, "user-john-doe")
|
||||
}
|
||||
|
||||
if event.Metadata[MetadataKeyUserID] != "user-john-doe" {
|
||||
t.Errorf("expected userId key to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_TraceIDHelpers(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-trace",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
event.SetTraceID("trace-abc-def-ghi")
|
||||
if got := event.GetTraceID(); got != "trace-abc-def-ghi" {
|
||||
t.Errorf("GetTraceID mismatch: got %q, want %q", got, "trace-abc-def-ghi")
|
||||
}
|
||||
|
||||
if event.Metadata[MetadataKeyTraceID] != "trace-abc-def-ghi" {
|
||||
t.Errorf("expected traceId key to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_SpanIDHelpers(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-span",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
event.SetSpanID("span-123-456")
|
||||
if got := event.GetSpanID(); got != "span-123-456" {
|
||||
t.Errorf("GetSpanID mismatch: got %q, want %q", got, "span-123-456")
|
||||
}
|
||||
|
||||
if event.Metadata[MetadataKeySpanID] != "span-123-456" {
|
||||
t.Errorf("expected spanId key to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_WithMetadataFrom(t *testing.T) {
|
||||
source := &Event{
|
||||
ID: "evt-source",
|
||||
EventType: "SourceEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Metadata: map[string]string{
|
||||
"correlationId": "corr-123",
|
||||
"causationId": "cause-456",
|
||||
"customKey": "customValue",
|
||||
},
|
||||
}
|
||||
|
||||
target := &Event{
|
||||
ID: "evt-target",
|
||||
EventType: "TargetEvent",
|
||||
ActorID: "actor-456",
|
||||
Version: 2,
|
||||
}
|
||||
|
||||
// Copy metadata from source to target
|
||||
target.WithMetadataFrom(source)
|
||||
|
||||
if target.Metadata == nil {
|
||||
t.Fatal("expected Metadata to be initialized after WithMetadataFrom")
|
||||
}
|
||||
if target.Metadata["correlationId"] != "corr-123" {
|
||||
t.Errorf("correlationId not copied: got %q", target.Metadata["correlationId"])
|
||||
}
|
||||
if target.Metadata["causationId"] != "cause-456" {
|
||||
t.Errorf("causationId not copied: got %q", target.Metadata["causationId"])
|
||||
}
|
||||
if target.Metadata["customKey"] != "customValue" {
|
||||
t.Errorf("customKey not copied: got %q", target.Metadata["customKey"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_WithMetadataFromNilSource(t *testing.T) {
|
||||
target := &Event{
|
||||
ID: "evt-target",
|
||||
EventType: "TargetEvent",
|
||||
ActorID: "actor-456",
|
||||
Version: 2,
|
||||
}
|
||||
|
||||
// WithMetadataFrom should handle nil source gracefully
|
||||
target.WithMetadataFrom(nil)
|
||||
if target.Metadata != nil {
|
||||
t.Error("expected Metadata to remain nil after WithMetadataFrom(nil)")
|
||||
}
|
||||
|
||||
// WithMetadataFrom should handle source with nil metadata
|
||||
source := &Event{
|
||||
ID: "evt-source",
|
||||
EventType: "SourceEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Metadata: nil,
|
||||
}
|
||||
target.WithMetadataFrom(source)
|
||||
if target.Metadata != nil {
|
||||
t.Error("expected Metadata to remain nil when source has nil Metadata")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_WithMetadataFromPreservesExisting(t *testing.T) {
|
||||
source := &Event{
|
||||
ID: "evt-source",
|
||||
EventType: "SourceEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Metadata: map[string]string{
|
||||
"sourceKey": "sourceValue",
|
||||
},
|
||||
}
|
||||
|
||||
target := &Event{
|
||||
ID: "evt-target",
|
||||
EventType: "TargetEvent",
|
||||
ActorID: "actor-456",
|
||||
Version: 2,
|
||||
Metadata: map[string]string{
|
||||
"existingKey": "existingValue",
|
||||
},
|
||||
}
|
||||
|
||||
target.WithMetadataFrom(source)
|
||||
|
||||
// Both keys should be present
|
||||
if target.Metadata["existingKey"] != "existingValue" {
|
||||
t.Errorf("existingKey was lost: got %q", target.Metadata["existingKey"])
|
||||
}
|
||||
if target.Metadata["sourceKey"] != "sourceValue" {
|
||||
t.Errorf("sourceKey not copied: got %q", target.Metadata["sourceKey"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_MetadataSpecialCharacters(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-special-meta",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Metadata: map[string]string{
|
||||
"unicode": "Hello, \u4e16\u754c!",
|
||||
"newlines": "line1\nline2",
|
||||
"quotes": `"quoted"`,
|
||||
"backslash": `path\to\file`,
|
||||
"empty": "",
|
||||
"whitespace": " spaces ",
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal Event with special metadata: %v", err)
|
||||
}
|
||||
|
||||
var decoded Event
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("failed to unmarshal Event with special metadata: %v", err)
|
||||
}
|
||||
|
||||
for key, expected := range event.Metadata {
|
||||
if decoded.Metadata[key] != expected {
|
||||
t.Errorf("Metadata[%q] mismatch: got %q, want %q", key, decoded.Metadata[key], expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataKeyConstants(t *testing.T) {
|
||||
// Verify the constant values are as expected
|
||||
if MetadataKeyCorrelationID != "correlationId" {
|
||||
t.Errorf("MetadataKeyCorrelationID wrong: got %q", MetadataKeyCorrelationID)
|
||||
}
|
||||
if MetadataKeyCausationID != "causationId" {
|
||||
t.Errorf("MetadataKeyCausationID wrong: got %q", MetadataKeyCausationID)
|
||||
}
|
||||
if MetadataKeyUserID != "userId" {
|
||||
t.Errorf("MetadataKeyUserID wrong: got %q", MetadataKeyUserID)
|
||||
}
|
||||
if MetadataKeyTraceID != "traceId" {
|
||||
t.Errorf("MetadataKeyTraceID wrong: got %q", MetadataKeyTraceID)
|
||||
}
|
||||
if MetadataKeySpanID != "spanId" {
|
||||
t.Errorf("MetadataKeySpanID wrong: got %q", MetadataKeySpanID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_MetadataJSONFieldName(t *testing.T) {
|
||||
event := &Event{
|
||||
ID: "evt-field-name",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Data: map[string]interface{}{},
|
||||
Metadata: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal Event: %v", err)
|
||||
}
|
||||
|
||||
// Check for correct JSON field name (camelCase)
|
||||
if !strings.Contains(string(data), `"metadata"`) {
|
||||
t.Errorf("expected 'metadata' JSON field, got: %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_MetadataAllHelpersRoundTrip(t *testing.T) {
|
||||
// Test that all helper methods work together in a roundtrip
|
||||
event := &Event{
|
||||
ID: "evt-all-helpers",
|
||||
EventType: "TestEvent",
|
||||
ActorID: "actor-123",
|
||||
Version: 1,
|
||||
Data: map[string]interface{}{},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
event.SetCorrelationID("corr-1")
|
||||
event.SetCausationID("cause-2")
|
||||
event.SetUserID("user-3")
|
||||
event.SetTraceID("trace-4")
|
||||
event.SetSpanID("span-5")
|
||||
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal Event: %v", err)
|
||||
}
|
||||
|
||||
var decoded Event
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("failed to unmarshal Event: %v", err)
|
||||
}
|
||||
|
||||
if decoded.GetCorrelationID() != "corr-1" {
|
||||
t.Errorf("GetCorrelationID mismatch: got %q", decoded.GetCorrelationID())
|
||||
}
|
||||
if decoded.GetCausationID() != "cause-2" {
|
||||
t.Errorf("GetCausationID mismatch: got %q", decoded.GetCausationID())
|
||||
}
|
||||
if decoded.GetUserID() != "user-3" {
|
||||
t.Errorf("GetUserID mismatch: got %q", decoded.GetUserID())
|
||||
}
|
||||
if decoded.GetTraceID() != "trace-4" {
|
||||
t.Errorf("GetTraceID mismatch: got %q", decoded.GetTraceID())
|
||||
}
|
||||
if decoded.GetSpanID() != "span-5" {
|
||||
t.Errorf("GetSpanID mismatch: got %q", decoded.GetSpanID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1686,3 +1686,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user