use workflow_context::{ContextStore, Vgcs}; use std::io::Read; use tempfile::TempDir; use std::sync::Arc; use std::thread; const ZERO_OID: &str = "0000000000000000000000000000000000000000"; #[test] fn test_basic_workflow() -> anyhow::Result<()> { let temp_dir = TempDir::new()?; let store = Vgcs::new(temp_dir.path()); let req_id = "req-001"; // 1. Init store.init_repo(req_id)?; // 2. Write Transaction (Initial Commit) let mut tx = store.begin_transaction(req_id, ZERO_OID)?; tx.write("test.txt", b"Hello World")?; let commit_hash_1 = tx.commit("Initial commit", "Test User")?; // 3. Read let mut reader = store.read_file(req_id, &commit_hash_1, "test.txt")?; let mut content = String::new(); reader.read_to_string(&mut content)?; assert_eq!(content, "Hello World"); // 4. Modify file let mut tx = store.begin_transaction(req_id, &commit_hash_1)?; tx.write("test.txt", b"Hello World Modified")?; tx.write("new.txt", b"New File")?; let commit_hash_2 = tx.commit("Second commit", "Test User")?; // 5. Verify Diff let changes = store.diff(req_id, &commit_hash_1, &commit_hash_2)?; // Should have 1 Modified (test.txt) and 1 Added (new.txt) assert_eq!(changes.len(), 2); // 6. List Dir let entries = store.list_dir(req_id, &commit_hash_2, "")?; assert_eq!(entries.len(), 2); // test.txt, new.txt Ok(()) } #[test] fn test_large_file_support() -> anyhow::Result<()> { let temp_dir = TempDir::new()?; let store = Vgcs::new(temp_dir.path()); let req_id = "req-large"; store.init_repo(req_id)?; // Create 2MB data let large_data = vec![b'a'; 2 * 1024 * 1024]; let mut tx = store.begin_transaction(req_id, ZERO_OID)?; tx.write("large.bin", &large_data)?; let commit_hash = tx.commit("Add large file", "Tester")?; // Read back let mut reader = store.read_file(req_id, &commit_hash, "large.bin")?; let mut read_data = Vec::new(); reader.read_to_end(&mut read_data)?; assert_eq!(read_data.len(), large_data.len()); // Checking first and last bytes to be reasonably sure without comparing 2MB in assertion message on failure assert_eq!(read_data[0], b'a'); assert_eq!(read_data[read_data.len()-1], b'a'); assert_eq!(read_data, large_data); // Check internal blob store // We don't calculate SHA256 here to verify path exactly, but we check if blobs dir has content let blobs_dir = temp_dir.path().join("blobs").join(req_id); assert!(blobs_dir.exists()); // Should have subdirectories for SHA prefix let entries = std::fs::read_dir(blobs_dir)?.collect::>(); assert!(!entries.is_empty()); Ok(()) } #[test] fn test_parallel_branching_and_merge() -> anyhow::Result<()> { let temp_dir = TempDir::new()?; // Clone temp_path for threads let temp_path = temp_dir.path().to_path_buf(); let store = Arc::new(Vgcs::new(&temp_path)); let req_id = "req-parallel"; store.init_repo(req_id)?; // Initial commit let base_commit = { let mut tx = store.begin_transaction(req_id, ZERO_OID)?; tx.write("base.txt", b"Base Content")?; tx.commit("Base Commit", "System")? }; // Fork 1: Modify base.txt let store1 = store.clone(); let base1 = base_commit.clone(); let handle1 = thread::spawn(move || -> anyhow::Result { let mut tx = store1.begin_transaction(req_id, &base1)?; tx.write("base.txt", b"Base Content Modified by 1")?; tx.write("file1.txt", b"File 1 Content")?; Ok(tx.commit("Fork 1 Commit", "User 1")?) }); // Fork 2: Add file2.txt (No conflict) let store2 = store.clone(); let base2 = base_commit.clone(); let handle2 = thread::spawn(move || -> anyhow::Result { let mut tx = store2.begin_transaction(req_id, &base2)?; tx.write("file2.txt", b"File 2 Content")?; Ok(tx.commit("Fork 2 Commit", "User 2")?) }); let commit1 = handle1.join().unwrap()?; let commit2 = handle2.join().unwrap()?; // Merge Fork 2 into Fork 1 (Memory Merge) // This merge should succeed as they touch different files/areas (mostly) // But wait, Fork 1 modified base.txt, Fork 2 kept it as is. // Git merge should take Fork 1's change and include Fork 2's new file. // We need to commit the merge result to verify it let merge_tree_oid = store.merge_trees(req_id, &base_commit, &commit1, &commit2)?; // Manually create a commit from the merge tree to verify content (optional but good) // In real system, Orchestrator would do this. // For test, we can just verify the tree contains what we expect or use a helper. // Or we can just trust merge_trees returns an OID on success. assert!(!merge_tree_oid.is_empty()); Ok(()) } #[test] fn test_merge_conflict() -> anyhow::Result<()> { let temp_dir = TempDir::new()?; let store = Vgcs::new(temp_dir.path()); let req_id = "req-conflict"; store.init_repo(req_id)?; // Base let mut tx = store.begin_transaction(req_id, ZERO_OID)?; tx.write("conflict.txt", b"Base Version")?; let base_commit = tx.commit("Base", "System")?; // Branch A: Edit conflict.txt let mut tx_a = store.begin_transaction(req_id, &base_commit)?; tx_a.write("conflict.txt", b"Version A")?; let commit_a = tx_a.commit("Commit A", "User A")?; // Branch B: Edit conflict.txt differently let mut tx_b = store.begin_transaction(req_id, &base_commit)?; tx_b.write("conflict.txt", b"Version B")?; let commit_b = tx_b.commit("Commit B", "User B")?; // Try Merge let result = store.merge_trees(req_id, &base_commit, &commit_a, &commit_b); // Should fail with conflict assert!(result.is_err()); let err = result.unwrap_err(); assert!(err.to_string().contains("Merge conflict")); Ok(()) }