forked from ryyst/kalzu-value-store
		
	Add conflict resolution testing and verify functionality
Added: - test_conflict.go utility to create timestamp collision scenarios - Verified sophisticated conflict resolution works correctly Test Results: ✅ Successfully created conflicting data with identical timestamps ✅ Conflict resolution triggered during sync cycle ✅ Majority vote system activated (2-node scenario) ✅ Oldest node tie-breaker correctly applied ✅ Remote data won based on older joined timestamp ✅ Local data was properly replaced with winning version ✅ Detailed logging showed complete decision process Logs showed the complete flow: 1. "Timestamp collision detected, starting conflict resolution" 2. "Starting conflict resolution with majority vote" 3. "Resolved conflict using oldest node tie-breaker" 4. "Conflict resolved: remote data wins" 5. "Conflict resolved, updated local data" The sophisticated conflict resolution system works exactly as designed! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										101
									
								
								test_conflict.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								test_conflict.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						badger "github.com/dgraph-io/badger/v4"
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StoredValue matches the structure in main.go
 | 
				
			||||||
 | 
					type StoredValue struct {
 | 
				
			||||||
 | 
						UUID      string          `json:"uuid"`
 | 
				
			||||||
 | 
						Timestamp int64           `json:"timestamp"`
 | 
				
			||||||
 | 
						Data      json.RawMessage `json:"data"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test utility to create conflicting data directly in BadgerDB
 | 
				
			||||||
 | 
					func createConflictingData(dataDir1, dataDir2 string) error {
 | 
				
			||||||
 | 
						// Same timestamp, different UUIDs
 | 
				
			||||||
 | 
						timestamp := time.Now().UnixMilli()
 | 
				
			||||||
 | 
						path := "test/conflict/data"
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Data for node1
 | 
				
			||||||
 | 
						data1 := json.RawMessage(`{"message": "from node1", "value": 100}`)
 | 
				
			||||||
 | 
						uuid1 := uuid.New().String()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Data for node2 (same timestamp, different UUID and content)
 | 
				
			||||||
 | 
						data2 := json.RawMessage(`{"message": "from node2", "value": 200}`)
 | 
				
			||||||
 | 
						uuid2 := uuid.New().String()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Store in node1's database
 | 
				
			||||||
 | 
						err := storeConflictData(dataDir1, path, timestamp, uuid1, data1)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to store in node1: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Store in node2's database  
 | 
				
			||||||
 | 
						err = storeConflictData(dataDir2, path, timestamp, uuid2, data2)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to store in node2: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						fmt.Printf("Created conflict scenario:\n")
 | 
				
			||||||
 | 
						fmt.Printf("Path: %s\n", path)
 | 
				
			||||||
 | 
						fmt.Printf("Timestamp: %d\n", timestamp)
 | 
				
			||||||
 | 
						fmt.Printf("Node1 UUID: %s, Data: %s\n", uuid1, string(data1))
 | 
				
			||||||
 | 
						fmt.Printf("Node2 UUID: %s, Data: %s\n", uuid2, string(data2))
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func storeConflictData(dataDir, path string, timestamp int64, uuid string, data json.RawMessage) error {
 | 
				
			||||||
 | 
						opts := badger.DefaultOptions(dataDir + "/badger")
 | 
				
			||||||
 | 
						opts.Logger = nil
 | 
				
			||||||
 | 
						db, err := badger.Open(opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer db.Close()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						storedValue := StoredValue{
 | 
				
			||||||
 | 
							UUID:      uuid,
 | 
				
			||||||
 | 
							Timestamp: timestamp,
 | 
				
			||||||
 | 
							Data:      data,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						valueBytes, err := json.Marshal(storedValue)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return db.Update(func(txn *badger.Txn) error {
 | 
				
			||||||
 | 
							// Store main data
 | 
				
			||||||
 | 
							if err := txn.Set([]byte(path), valueBytes); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Store timestamp index
 | 
				
			||||||
 | 
							indexKey := fmt.Sprintf("_ts:%020d:%s", timestamp, path)
 | 
				
			||||||
 | 
							return txn.Set([]byte(indexKey), []byte(uuid))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						if len(os.Args) < 3 {
 | 
				
			||||||
 | 
							fmt.Println("Usage: go run test_conflict.go <data_dir1> <data_dir2>")
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						err := createConflictingData(os.Args[1], os.Args[2])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Printf("Error: %v\n", err)
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						fmt.Println("Conflict data created successfully!")
 | 
				
			||||||
 | 
						fmt.Println("Start your nodes and trigger a sync to see conflict resolution in action.")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user