chane335/permanence
1
1"""Tests for permanence.world.git — reversibility + isolation."""2from __future__ import annotations3 4import socket5import subprocess6from unittest.mock import patch7 8from permanence.world.git import MockGitRepo9 10 11def test_initial_commit_and_log_are_r1_r2():12 repo = MockGitRepo()13 res = repo.commit("add feature", {"src/main.py": b"print(1)"})14 assert res.ok and res.r_level == 215 log_res = repo.log()16 assert log_res.ok and log_res.r_level == 117 18 19def test_reset_hard_is_r3_via_reflog():20 repo = MockGitRepo()21 repo.commit("a", {"a": b"1"})22 repo.commit("b", {"a": b"1", "b": b"2"})23 res = repo.reset_hard(1)24 assert res.ok and res.r_level == 325 assert len(res.orphaned_commits) == 126 27 28def test_reset_hard_after_reflog_expired_is_r4():29 repo = MockGitRepo()30 repo.commit("a", {"a": b"1"})31 repo.commit("b", {"a": b"1", "b": b"2"})32 repo.reflog_expire_all()33 res = repo.reset_hard(1)34 assert res.ok and res.r_level == 435 36 37def test_branch_delete_with_reflog_is_r3():38 repo = MockGitRepo()39 repo.checkout_branch("feature", create=True)40 repo.commit("feature work", {"x": b"1"})41 repo.checkout_branch("main")42 res = repo.delete_branch("feature")43 assert res.ok and res.r_level == 344 assert len(res.orphaned_commits) == 145 46 47def test_push_is_r2_and_force_push_preserved_elsewhere_is_r4():48 repo = MockGitRepo()49 # Make two local commits then push normally50 repo.commit("a", {"a": b"1"})51 repo.commit("b", {"a": b"1", "b": b"2"})52 push = repo.push()53 assert push.ok and push.r_level == 254 55 # Now rewrite history locally (reset past the second commit)56 repo.reset_hard(1)57 # The previous commit b is preserved on someone else's clone (modeled)58 remote_tip = repo.remote_branches["main"]59 repo.other_clones_have_commits.add(remote_tip)60 res = repo.push_force()61 assert res.ok and res.r_level == 462 63 64def test_force_push_without_preservation_is_r5():65 repo = MockGitRepo()66 repo.commit("a", {"a": b"1"})67 repo.commit("b", {"a": b"1", "b": b"2"})68 repo.push()69 repo.reset_hard(1) # Drop commit b locally70 # Nobody has b anywhere71 res = repo.push_force()72 assert res.ok and res.r_level == 573 assert len(res.orphaned_commits) >= 174 75 76def test_reflog_expire_all_with_orphans_is_r5():77 repo = MockGitRepo()78 repo.commit("a", {"a": b"1"})79 repo.commit("b", {"a": b"1", "b": b"2"})80 repo.reset_hard(1) # b now only lives in reflog81 res = repo.reflog_expire_all()82 assert res.ok and res.r_level == 583 84 85def test_filter_branch_rewrites_history_r4():86 repo = MockGitRepo()87 repo.commit("add secrets", {"src/main.py": b"x", "secrets.env": b"KEY=abc"})88 repo.commit("more work", {"src/main.py": b"y", "secrets.env": b"KEY=abc"})89 res = repo.filter_branch_drop("secrets.env")90 assert res.ok and res.r_level == 491 # Every new commit's files should lack secrets.env92 tip = repo.branches["main"]93 assert "secrets.env" not in repo.commits[tip].files94 95 96def test_mock_git_never_shells_out_or_hits_network():97 """Same isolation guarantee as the mock FS."""98 with patch.object(subprocess, "run") as mock_run, patch.object(99 subprocess, "Popen"100 ) as mock_popen, patch.object(socket, "socket") as mock_sock:101 repo = MockGitRepo()102 repo.commit("x", {"a": b"1"})103 repo.checkout_branch("feat", create=True)104 repo.commit("y", {"a": b"2"})105 repo.checkout_branch("main")106 repo.delete_branch("feat")107 repo.push()108 repo.reset_hard(1)109 repo.push_force()110 assert mock_run.call_count == 0111 assert mock_popen.call_count == 0112 assert mock_sock.call_count == 0113 