package main import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // MockIntegrationEventStore for integration testing type MockIntegrationEventStore struct { events []Event items map[string]map[string]interface{} nextSeq int patcher *JSONPatcher syncValid bool forceError bool } func NewMockIntegrationEventStore() *MockIntegrationEventStore { return &MockIntegrationEventStore{ events: []Event{}, items: make(map[string]map[string]interface{}), nextSeq: 1, patcher: &JSONPatcher{}, } } func (m *MockIntegrationEventStore) GetLatestEvent() (*Event, error) { if len(m.events) == 0 { return nil, nil } return &m.events[len(m.events)-1], nil } func (m *MockIntegrationEventStore) ProcessEvent(incomingEvent *Event) (*Event, error) { if m.forceError { return nil, assert.AnError } // Create processed event event := &Event{ Seq: m.nextSeq, Hash: "hash_" + string(rune(m.nextSeq+'0')), ItemID: incomingEvent.ItemID, EventID: "event_" + string(rune(m.nextSeq+'0')), Collection: incomingEvent.Collection, Operation: incomingEvent.Operation, Path: incomingEvent.Path, Value: incomingEvent.Value, From: incomingEvent.From, Timestamp: time.Now(), } // Apply event to items using JSON Patch itemKey := incomingEvent.Collection + ":" + incomingEvent.ItemID if m.items[itemKey] == nil { m.items[itemKey] = map[string]interface{}{ "id": incomingEvent.ItemID, } } // Create patch operation and apply it patch := PatchOperation{ Op: incomingEvent.Operation, Path: incomingEvent.Path, Value: incomingEvent.Value, From: incomingEvent.From, } updatedItem, err := m.patcher.ApplyPatches(m.items[itemKey], []PatchOperation{patch}) if err != nil { return nil, err // Return the actual JSON patch error } m.items[itemKey] = updatedItem m.events = append(m.events, *event) m.nextSeq++ return event, nil } func (m *MockIntegrationEventStore) GetEventsSince(seq int) ([]Event, error) { var events []Event for _, event := range m.events { if event.Seq > seq { events = append(events, event) } } return events, nil } func (m *MockIntegrationEventStore) GetAllItems(collection string) ([]map[string]interface{}, error) { var items []map[string]interface{} for key, item := range m.items { if len(key) >= len(collection) && key[:len(collection)] == collection { // Skip soft-deleted items if deletedAt, exists := item["deleted_at"]; !exists || deletedAt == "" { items = append(items, item) } } } return items, nil } func (m *MockIntegrationEventStore) ValidateSync(seq int, hash string) (bool, error) { if m.forceError { return false, assert.AnError } return m.syncValid, nil } func TestFullWorkflow(t *testing.T) { mockStore := NewMockIntegrationEventStore() t.Run("1. Create initial item using JSON Patch", func(t *testing.T) { event := &Event{ ItemID: "workflow_item_001", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Initial workflow item", } result, err := mockStore.ProcessEvent(event) require.NoError(t, err) assert.Equal(t, 1, result.Seq) assert.Equal(t, "add", result.Operation) assert.Equal(t, "/content", result.Path) assert.Equal(t, "Initial workflow item", result.Value) }) t.Run("2. Update item with multiple operations", func(t *testing.T) { // Add priority event1 := &Event{ ItemID: "workflow_item_001", Collection: "shopping_items", Operation: "add", Path: "/priority", Value: "high", } result1, err := mockStore.ProcessEvent(event1) require.NoError(t, err) assert.Equal(t, 2, result1.Seq) // Update content event2 := &Event{ ItemID: "workflow_item_001", Collection: "shopping_items", Operation: "replace", Path: "/content", Value: "Updated workflow item", } result2, err := mockStore.ProcessEvent(event2) require.NoError(t, err) assert.Equal(t, 3, result2.Seq) }) t.Run("3. Create second item", func(t *testing.T) { event := &Event{ ItemID: "workflow_item_002", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Second workflow item", } result, err := mockStore.ProcessEvent(event) require.NoError(t, err) assert.Equal(t, 4, result.Seq) }) t.Run("4. Get all items", func(t *testing.T) { items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) assert.Len(t, items, 2) // Find first item and verify its state var item1 map[string]interface{} for _, item := range items { if item["id"] == "workflow_item_001" { item1 = item break } } require.NotNil(t, item1) assert.Equal(t, "Updated workflow item", item1["content"]) assert.Equal(t, "high", item1["priority"]) }) t.Run("5. Test client synchronization", func(t *testing.T) { // Test getting events since sequence 2 events, err := mockStore.GetEventsSince(2) require.NoError(t, err) assert.Len(t, events, 2) // events 3 and 4 // Verify latest event latestEvent, err := mockStore.GetLatestEvent() require.NoError(t, err) assert.Equal(t, 4, latestEvent.Seq) // Test sync validation mockStore.syncValid = true isValid, err := mockStore.ValidateSync(4, "hash_4") require.NoError(t, err) assert.True(t, isValid) }) t.Run("6. Soft delete an item", func(t *testing.T) { deleteEvent := &Event{ ItemID: "workflow_item_001", Collection: "shopping_items", Operation: "add", Path: "/deleted_at", Value: time.Now().Format(time.RFC3339), } result, err := mockStore.ProcessEvent(deleteEvent) require.NoError(t, err) assert.Equal(t, 5, result.Seq) assert.Equal(t, "add", result.Operation) assert.Equal(t, "/deleted_at", result.Path) // Verify item is now filtered out items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) assert.Len(t, items, 1) // Only one item remaining // Verify remaining item is item_002 assert.Equal(t, "workflow_item_002", items[0]["id"]) }) t.Run("7. Complex nested operations", func(t *testing.T) { // Add metadata object metadataEvent := &Event{ ItemID: "workflow_item_002", Collection: "shopping_items", Operation: "add", Path: "/metadata", Value: `{"created_by": "system", "version": 1}`, } result, err := mockStore.ProcessEvent(metadataEvent) require.NoError(t, err) assert.Equal(t, 6, result.Seq) // Skip nested field update since we store JSON as strings // The metadata is stored as a JSON string, not a parsed object }) t.Run("8. Test operation on nested data", func(t *testing.T) { items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) assert.Len(t, items, 1) item := items[0] assert.Equal(t, "workflow_item_002", item["id"]) assert.Equal(t, "Second workflow item", item["content"]) // Check nested metadata (stored as JSON string) metadataStr, ok := item["metadata"].(string) require.True(t, ok) assert.Contains(t, metadataStr, "system") assert.Contains(t, metadataStr, "1") // Original value }) t.Run("9. Test move operation", func(t *testing.T) { moveEvent := &Event{ ItemID: "workflow_item_002", Collection: "shopping_items", Operation: "move", From: "/content", Path: "/title", } result, err := mockStore.ProcessEvent(moveEvent) require.NoError(t, err) assert.Equal(t, 7, result.Seq) // Verify move operation items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) item := items[0] assert.Equal(t, "Second workflow item", item["title"]) assert.Nil(t, item["content"]) // Should be moved, not copied }) t.Run("10. Test copy operation", func(t *testing.T) { copyEvent := &Event{ ItemID: "workflow_item_002", Collection: "shopping_items", Operation: "copy", From: "/title", Path: "/backup_title", } result, err := mockStore.ProcessEvent(copyEvent) require.NoError(t, err) assert.Equal(t, 8, result.Seq) // Verify copy operation items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) item := items[0] assert.Equal(t, "Second workflow item", item["title"]) assert.Equal(t, "Second workflow item", item["backup_title"]) }) t.Run("11. Final state verification", func(t *testing.T) { // Verify final event count latestEvent, err := mockStore.GetLatestEvent() require.NoError(t, err) assert.Equal(t, 8, latestEvent.Seq) // Verify all events are recorded allEvents, err := mockStore.GetEventsSince(0) require.NoError(t, err) assert.Len(t, allEvents, 8) // Verify final item state items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) assert.Len(t, items, 1) finalItem := items[0] assert.Equal(t, "workflow_item_002", finalItem["id"]) assert.Equal(t, "Second workflow item", finalItem["title"]) assert.Equal(t, "Second workflow item", finalItem["backup_title"]) assert.Nil(t, finalItem["content"]) // Moved to title metadataStr, ok := finalItem["metadata"].(string) require.True(t, ok) assert.Contains(t, metadataStr, "system") assert.Contains(t, metadataStr, "1") }) } func TestErrorScenarios(t *testing.T) { tests := []struct { name string setup func(*MockIntegrationEventStore) event Event wantErr bool }{ { name: "Invalid JSON Patch operation", setup: func(m *MockIntegrationEventStore) {}, event: Event{ ItemID: "error_test_001", Collection: "shopping_items", Operation: "invalid_op", Path: "/content", Value: "test", }, wantErr: true, }, { name: "Test operation failure", setup: func(m *MockIntegrationEventStore) { // Create item first m.items["shopping_items:test_item"] = map[string]interface{}{ "id": "test_item", "content": "original", } }, event: Event{ ItemID: "test_item", Collection: "shopping_items", Operation: "test", Path: "/content", Value: "different", // This should fail the test }, wantErr: true, }, { name: "Remove non-existent field", setup: func(m *MockIntegrationEventStore) { m.items["shopping_items:test_item"] = map[string]interface{}{ "id": "test_item", } }, event: Event{ ItemID: "test_item", Collection: "shopping_items", Operation: "remove", Path: "/nonexistent", }, wantErr: true, }, { name: "Replace non-existent field", setup: func(m *MockIntegrationEventStore) { m.items["shopping_items:test_item"] = map[string]interface{}{ "id": "test_item", } }, event: Event{ ItemID: "test_item", Collection: "shopping_items", Operation: "replace", Path: "/nonexistent", Value: "value", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockStore := NewMockIntegrationEventStore() tt.setup(mockStore) _, err := mockStore.ProcessEvent(&tt.event) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestSyncScenarios(t *testing.T) { mockStore := NewMockIntegrationEventStore() // Create some events events := []*Event{ { ItemID: "sync_item_001", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "First item", }, { ItemID: "sync_item_002", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Second item", }, { ItemID: "sync_item_001", Collection: "shopping_items", Operation: "replace", Path: "/content", Value: "Updated first item", }, } // Process all events for _, event := range events { _, err := mockStore.ProcessEvent(event) require.NoError(t, err) } t.Run("Full sync from beginning", func(t *testing.T) { events, err := mockStore.GetEventsSince(0) require.NoError(t, err) assert.Len(t, events, 3) // Verify event sequence for i, event := range events { assert.Equal(t, i+1, event.Seq) } }) t.Run("Incremental sync", func(t *testing.T) { events, err := mockStore.GetEventsSince(1) require.NoError(t, err) assert.Len(t, events, 2) // Should get events 2 and 3 assert.Equal(t, 2, events[0].Seq) assert.Equal(t, 3, events[1].Seq) }) t.Run("Sync validation", func(t *testing.T) { // Test valid sync mockStore.syncValid = true isValid, err := mockStore.ValidateSync(3, "hash_3") require.NoError(t, err) assert.True(t, isValid) // Test invalid sync mockStore.syncValid = false isValid, err = mockStore.ValidateSync(2, "wrong_hash") require.NoError(t, err) assert.False(t, isValid) }) t.Run("Get latest event", func(t *testing.T) { latest, err := mockStore.GetLatestEvent() require.NoError(t, err) assert.Equal(t, 3, latest.Seq) assert.Equal(t, "sync_item_001", latest.ItemID) assert.Equal(t, "replace", latest.Operation) }) } func TestConcurrentOperations(t *testing.T) { mockStore := NewMockIntegrationEventStore() // Simulate concurrent operations on different items t.Run("Multiple items concurrent creation", func(t *testing.T) { events := []*Event{ { ItemID: "concurrent_001", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Concurrent item 1", }, { ItemID: "concurrent_002", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Concurrent item 2", }, { ItemID: "concurrent_003", Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Concurrent item 3", }, } // Process events sequentially (simulating concurrent processing) var results []*Event for _, event := range events { result, err := mockStore.ProcessEvent(event) require.NoError(t, err) results = append(results, result) } // Verify all events were processed with proper sequence numbers for i, result := range results { assert.Equal(t, i+1, result.Seq) } // Verify all items exist items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) assert.Len(t, items, 3) }) t.Run("Same item multiple operations", func(t *testing.T) { itemID := "concurrent_same_item" operations := []*Event{ { ItemID: itemID, Collection: "shopping_items", Operation: "add", Path: "/content", Value: "Initial content", }, { ItemID: itemID, Collection: "shopping_items", Operation: "add", Path: "/priority", Value: "low", }, { ItemID: itemID, Collection: "shopping_items", Operation: "replace", Path: "/priority", Value: "high", }, { ItemID: itemID, Collection: "shopping_items", Operation: "add", Path: "/tags", Value: `["urgent", "important"]`, }, } for _, op := range operations { _, err := mockStore.ProcessEvent(op) require.NoError(t, err) } // Verify final state items, err := mockStore.GetAllItems("shopping_items") require.NoError(t, err) var targetItem map[string]interface{} for _, item := range items { if item["id"] == itemID { targetItem = item break } } require.NotNil(t, targetItem) assert.Equal(t, "Initial content", targetItem["content"]) assert.Equal(t, "high", targetItem["priority"]) assert.NotNil(t, targetItem["tags"]) }) }