(** Unit tests for Day10_lib.Atomic_swap module. These tests verify the atomic swap mechanism for graceful degradation: - Successful swaps replace old docs with new - Failed builds preserve existing docs - Interrupted swaps are recovered on startup *) let test_dir = ref "" let setup () = let dir = Filename.temp_dir "test-atomic-swap-" "" in test_dir := dir; (* Create html/p structure *) let html_dir = Filename.concat dir "html" in let p_dir = Filename.concat html_dir "p" in Unix.mkdir html_dir 0o755; Unix.mkdir p_dir 0o755; html_dir let teardown () = if !test_dir <> "" then begin ignore (Sys.command (Printf.sprintf "rm -rf %s" !test_dir)); test_dir := "" end let write_file path content = let oc = open_out path in output_string oc content; close_out oc let read_file path = let ic = open_in path in let content = really_input_string ic (in_channel_length ic) in close_in ic; content let file_exists path = Sys.file_exists path (** Test: cleanup_stale_dirs removes .new directories *) let test_cleanup_stale_new () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; let stale_new = Filename.concat pkg_dir "1.0.0.new" in Unix.mkdir stale_new 0o755; write_file (Filename.concat stale_new "index.html") "stale content"; assert (file_exists stale_new); Day10_lib.Atomic_swap.cleanup_stale_dirs ~html_dir; assert (not (file_exists stale_new)); teardown (); Printf.printf "PASS: test_cleanup_stale_new\n%!" (** Test: cleanup_stale_dirs removes .old directories *) let test_cleanup_stale_old () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; let stale_old = Filename.concat pkg_dir "1.0.0.old" in Unix.mkdir stale_old 0o755; write_file (Filename.concat stale_old "index.html") "stale old content"; assert (file_exists stale_old); Day10_lib.Atomic_swap.cleanup_stale_dirs ~html_dir; assert (not (file_exists stale_old)); teardown (); Printf.printf "PASS: test_cleanup_stale_old\n%!" (** Test: cleanup_stale_dirs preserves normal directories *) let test_cleanup_preserves_normal () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; let normal_dir = Filename.concat pkg_dir "1.0.0" in Unix.mkdir normal_dir 0o755; write_file (Filename.concat normal_dir "index.html") "good content"; Day10_lib.Atomic_swap.cleanup_stale_dirs ~html_dir; assert (file_exists normal_dir); assert (read_file (Filename.concat normal_dir "index.html") = "good content"); teardown (); Printf.printf "PASS: test_cleanup_preserves_normal\n%!" (** Test: get_swap_paths returns correct paths for blessed packages *) let test_swap_paths_blessed () = let html_dir = "/test/html" in let staging, final, old = Day10_lib.Atomic_swap.get_swap_paths ~html_dir ~pkg:"my-pkg" ~version:"2.0.0" ~blessed:true ~universe:"ignored" in assert (staging = "/test/html/p/my-pkg/2.0.0.new"); assert (final = "/test/html/p/my-pkg/2.0.0"); assert (old = "/test/html/p/my-pkg/2.0.0.old"); Printf.printf "PASS: test_swap_paths_blessed\n%!" (** Test: get_swap_paths returns correct paths for universe packages *) let test_swap_paths_universe () = let html_dir = "/test/html" in let staging, final, old = Day10_lib.Atomic_swap.get_swap_paths ~html_dir ~pkg:"my-pkg" ~version:"2.0.0" ~blessed:false ~universe:"abc123" in assert (staging = "/test/html/u/abc123/my-pkg/2.0.0.new"); assert (final = "/test/html/u/abc123/my-pkg/2.0.0"); assert (old = "/test/html/u/abc123/my-pkg/2.0.0.old"); Printf.printf "PASS: test_swap_paths_universe\n%!" (** Test: commit swaps staging to final *) let test_commit_success () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; (* Create staging directory with new content *) let staging_dir = Filename.concat pkg_dir "1.0.0.new" in Unix.mkdir staging_dir 0o755; write_file (Filename.concat staging_dir "index.html") "new content"; let result = Day10_lib.Atomic_swap.commit ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"" in assert result; (* Verify staging was moved to final *) let final_dir = Filename.concat pkg_dir "1.0.0" in assert (file_exists final_dir); assert (read_file (Filename.concat final_dir "index.html") = "new content"); assert (not (file_exists staging_dir)); teardown (); Printf.printf "PASS: test_commit_success\n%!" (** Test: commit replaces existing with atomic swap *) let test_commit_replaces_existing () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; (* Create existing final directory with old content *) let final_dir = Filename.concat pkg_dir "1.0.0" in Unix.mkdir final_dir 0o755; write_file (Filename.concat final_dir "index.html") "old content"; (* Create staging directory with new content *) let staging_dir = Filename.concat pkg_dir "1.0.0.new" in Unix.mkdir staging_dir 0o755; write_file (Filename.concat staging_dir "index.html") "new content"; let result = Day10_lib.Atomic_swap.commit ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"" in assert result; (* Verify old was replaced with new *) assert (read_file (Filename.concat final_dir "index.html") = "new content"); assert (not (file_exists staging_dir)); (* .old should be cleaned up *) assert (not (file_exists (Filename.concat pkg_dir "1.0.0.old"))); teardown (); Printf.printf "PASS: test_commit_replaces_existing\n%!" (** Test: rollback removes staging, preserves existing *) let test_rollback_preserves_existing () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; (* Create existing final directory *) let final_dir = Filename.concat pkg_dir "1.0.0" in Unix.mkdir final_dir 0o755; write_file (Filename.concat final_dir "index.html") "original content"; (* Create staging directory (simulating failed build) *) let staging_dir = Filename.concat pkg_dir "1.0.0.new" in Unix.mkdir staging_dir 0o755; write_file (Filename.concat staging_dir "index.html") "incomplete content"; Day10_lib.Atomic_swap.rollback ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:""; (* Verify staging was removed but original preserved *) assert (not (file_exists staging_dir)); assert (file_exists final_dir); assert (read_file (Filename.concat final_dir "index.html") = "original content"); teardown (); Printf.printf "PASS: test_rollback_preserves_existing\n%!" (** Test: commit returns false when staging doesn't exist *) let test_commit_no_staging () = let html_dir = setup () in let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in Unix.mkdir pkg_dir 0o755; let result = Day10_lib.Atomic_swap.commit ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"" in assert (not result); teardown (); Printf.printf "PASS: test_commit_no_staging\n%!" let () = Printf.printf "Running atomic swap tests...\n%!"; test_cleanup_stale_new (); test_cleanup_stale_old (); test_cleanup_preserves_normal (); test_swap_paths_blessed (); test_swap_paths_universe (); test_commit_success (); test_commit_replaces_existing (); test_rollback_preserves_existing (); test_commit_no_staging (); Printf.printf "\nAll atomic swap tests passed!\n%!"