From 8cf2a5f1acba334bef4c075eed2a284dd7878596 Mon Sep 17 00:00:00 2001 From: Marco D'Aleo Date: Sun, 16 Nov 2025 14:58:19 +0000 Subject: [PATCH] Update tests for mirro's new flags --- tests/test_mirro.py | 253 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 206 insertions(+), 47 deletions(-) diff --git a/tests/test_mirro.py b/tests/test_mirro.py index f891e8a..fbb4753 100644 --- a/tests/test_mirro.py +++ b/tests/test_mirro.py @@ -54,11 +54,10 @@ def test_write_file(tmp_path): def test_backup_original(tmp_path, monkeypatch): - original_path = tmp_path / "test.txt" + original_path = tmp_path / "a.txt" original_content = "ABC" backup_dir = tmp_path / "backups" - # Freeze timestamps monkeypatch.setattr( time, "gmtime", @@ -78,14 +77,14 @@ def test_backup_original(tmp_path, monkeypatch): ) assert backup_path.exists() - text = backup_path.read_text(encoding="utf-8") + text = backup_path.read_text() assert "mirro backup" in text - assert "Original file:" in text - assert original_content in text + assert "Original file" in text + assert "ABC" in text # ============================================================ -# Helper to run main() +# Helper to simulate main() # ============================================================ @@ -100,11 +99,8 @@ def simulate_main( file_exists=True, override_access=None, ): - """Utility to simulate mirro.main()""" - monkeypatch.setenv("EDITOR", editor) - # Fake editor def fake_call(cmd): temp = Path(cmd[-1]) if edited_content is None: @@ -115,13 +111,11 @@ def simulate_main( monkeypatch.setattr(subprocess, "call", fake_call) - # Access override if provided if override_access: monkeypatch.setattr(os, "access", override_access) else: monkeypatch.setattr(os, "access", lambda p, m: True) - # Set up file as needed target = Path(args[-1]).expanduser().resolve() if file_exists: target.parent.mkdir(parents=True, exist_ok=True) @@ -135,7 +129,7 @@ def simulate_main( # ============================================================ -# main: missing file argument +# main: missing positional file # ============================================================ @@ -143,24 +137,23 @@ def test_main_missing_argument(capsys): with patch("sys.argv", ["mirro"]): with pytest.raises(SystemExit): mirro.main() - assert ( "the following arguments are required: file" in capsys.readouterr().err ) # ============================================================ -# main: unchanged file (line 137) +# main: unchanged file # ============================================================ def test_main_existing_unchanged(tmp_path, monkeypatch, capsys): target = tmp_path / "file.txt" - target.write_text("hello\n", encoding="utf-8") + target.write_text("hello\n") def fake_call(cmd): temp = Path(cmd[-1]) - temp.write_text("hello\n", encoding="utf-8") + temp.write_text("hello\n") monkeypatch.setenv("EDITOR", "nano") monkeypatch.setattr(subprocess, "call", fake_call) @@ -169,8 +162,7 @@ def test_main_existing_unchanged(tmp_path, monkeypatch, capsys): with patch("sys.argv", ["mirro", str(target)]): mirro.main() - out = capsys.readouterr().out - assert "file hasn't changed" in out + assert "file hasn't changed" in capsys.readouterr().out # ============================================================ @@ -179,7 +171,7 @@ def test_main_existing_unchanged(tmp_path, monkeypatch, capsys): def test_main_existing_changed(tmp_path, monkeypatch, capsys): - target = tmp_path / "file2.txt" + target = tmp_path / "f2.txt" result, out = simulate_main( monkeypatch, @@ -191,7 +183,7 @@ def test_main_existing_changed(tmp_path, monkeypatch, capsys): ) assert "file changed; original backed up at" in out - assert target.read_text(encoding="utf-8") == "new\n" + assert target.read_text() == "new\n" # ============================================================ @@ -233,68 +225,57 @@ def test_main_new_file_changed(tmp_path, monkeypatch, capsys): ) assert "file changed; original backed up at" in out - assert new.read_text(encoding="utf-8") == "XYZ\n" + assert new.read_text() == "XYZ\n" # ============================================================ -# main: permission denied for existing file (line 78) +# Permission denied branches # ============================================================ def test_main_permission_denied_existing(tmp_path, monkeypatch, capsys): - target = tmp_path / "blocked.txt" - target.write_text("hello", encoding="utf-8") + tgt = tmp_path / "blocked.txt" + tgt.write_text("hi") monkeypatch.setenv("EDITOR", "nano") monkeypatch.setattr(os, "access", lambda p, m: False) - with patch("sys.argv", ["mirro", str(target)]): + with patch("sys.argv", ["mirro", str(tgt)]): result = mirro.main() - out = capsys.readouterr().out - assert "Need elevated privileges to open" in out assert result == 1 - - -# ============================================================ -# main: permission denied creating file (line 84) -# ============================================================ + assert "Need elevated privileges to open" in capsys.readouterr().out def test_main_permission_denied_create(tmp_path, monkeypatch, capsys): - newfile = tmp_path / "subdir" / "nofile.txt" - parent = newfile.parent - parent.mkdir(parents=True, exist_ok=True) + new = tmp_path / "sub/xx.txt" + new.parent.mkdir(parents=True) - # Directory is not writable def fake_access(path, mode): - if path == parent: - return False - return True + return False if path == new.parent else True monkeypatch.setattr(os, "access", fake_access) monkeypatch.setenv("EDITOR", "nano") - with patch("sys.argv", ["mirro", str(newfile)]): + with patch("sys.argv", ["mirro", str(new)]): result = mirro.main() - out = capsys.readouterr().out - assert "Need elevated privileges to create" in out assert result == 1 + assert "Need elevated privileges to create" in capsys.readouterr().out # ============================================================ -# main: non-nano editor (ordering branch) +# Editor ordering: non-nano branch # ============================================================ def test_main_editor_non_nano(tmp_path, monkeypatch, capsys): target = tmp_path / "vim.txt" - target.write_text("old\n", encoding="utf-8") + target.write_text("old\n") def fake_call(cmd): - temp = Path(cmd[1]) # in non-nano mode - temp.write_text("edited\n", encoding="utf-8") + temp = Path(cmd[1]) + temp.write_text("edited\n") monkeypatch.setenv("EDITOR", "vim") monkeypatch.setattr(subprocess, "call", fake_call) @@ -303,4 +284,182 @@ def test_main_editor_non_nano(tmp_path, monkeypatch, capsys): with patch("sys.argv", ["mirro", str(target)]): mirro.main() - assert target.read_text(encoding="utf-8") == "edited\n" + assert target.read_text() == "edited\n" + + +# ============================================================ +# --list +# ============================================================ + + +def test_main_list_no_dir(tmp_path, capsys): + with patch( + "sys.argv", ["mirro", "--list", "--backup-dir", str(tmp_path / "none")] + ): + mirro.main() + assert "No backups found." in capsys.readouterr().out + + +def test_main_list_entries(tmp_path, capsys): + d = tmp_path / "bk" + d.mkdir() + (d / "a.txt.orig.1").write_text("x") + (d / "b.txt.orig.2").write_text("y") + + with patch("sys.argv", ["mirro", "--list", "--backup-dir", str(d)]): + mirro.main() + + out = capsys.readouterr().out + assert "a.txt.orig.1" in out + assert "b.txt.orig.2" in out + + +# ============================================================ +# --restore-last +# ============================================================ + + +def test_restore_last_no_dir(tmp_path, capsys): + d = tmp_path / "none" + target = tmp_path / "x.txt" + with patch( + "sys.argv", + ["mirro", "--restore-last", str(target), "--backup-dir", str(d)], + ): + result = mirro.main() + + assert result == 1 + assert "No backup directory found." in capsys.readouterr().out + + +def test_restore_last_no_backups(tmp_path, capsys): + d = tmp_path / "bk" + d.mkdir() + target = tmp_path / "t.txt" + + with patch( + "sys.argv", + ["mirro", "--restore-last", str(target), "--backup-dir", str(d)], + ): + result = mirro.main() + + assert result == 1 + assert "No backups found" in capsys.readouterr().out + + +def test_restore_last_success(tmp_path, capsys): + d = tmp_path / "bk" + d.mkdir() + target = tmp_path / "t.txt" + + b1 = d / "t.txt.orig.2020" + b2 = d / "t.txt.orig.2021" + + b1.write_text("# header\n\nold1") + b2.write_text("# header\n\nold2") + + # ensure newest + os.utime(b2, (time.time(), time.time())) + + with patch( + "sys.argv", + ["mirro", "--restore-last", str(target), "--backup-dir", str(d)], + ): + mirro.main() + + assert target.read_text() == "old2" + assert "Restored" in capsys.readouterr().out + + +# ============================================================ +# --prune-backups +# ============================================================ + + +def test_prune_all(tmp_path, capsys): + d = tmp_path / "bk" + d.mkdir() + (d / "a").write_text("x") + (d / "b").write_text("y") + + with patch( + "sys.argv", ["mirro", "--prune-backups=all", "--backup-dir", str(d)] + ): + mirro.main() + + out = capsys.readouterr().out + assert "Removed ALL backups" in out + assert not any(d.iterdir()) + + +def test_prune_numeric(tmp_path, capsys, monkeypatch): + d = tmp_path / "bk" + d.mkdir() + + old = d / "old" + new = d / "new" + old.write_text("x") + new.write_text("y") + + one_day_seconds = 86400 + + os.utime( + old, + ( + time.time() - one_day_seconds * 10, + time.time() - one_day_seconds * 10, + ), + ) + os.utime(new, None) + + with patch( + "sys.argv", ["mirro", "--prune-backups=5", "--backup-dir", str(d)] + ): + mirro.main() + + out = capsys.readouterr().out + assert "Removed 1 backup" in out + assert new.exists() + assert not old.exists() + + +def test_prune_default_env(tmp_path, monkeypatch, capsys): + monkeypatch.setenv("MIRRO_BACKUPS_LIFE", "1") + + d = tmp_path / "bk" + d.mkdir() + + f = d / "x" + f.write_text("hi") + + os.utime(f, (time.time() - 86400 * 2, time.time() - 86400 * 2)) + + with patch( + "sys.argv", ["mirro", "--prune-backups", "--backup-dir", str(d)] + ): + mirro.main() + + assert "Removed 1" in capsys.readouterr().out + + +def test_prune_invalid_env(tmp_path, monkeypatch, capsys): + monkeypatch.setenv("MIRRO_BACKUPS_LIFE", "nope") + + d = tmp_path / "bk" + d.mkdir() + + with patch( + "sys.argv", ["mirro", "--prune-backups", "--backup-dir", str(d)] + ): + mirro.main() + + out = capsys.readouterr().out + assert "Invalid MIRRO_BACKUPS_LIFE value" in out + + +def test_prune_invalid_arg(tmp_path, capsys): + with patch("sys.argv", ["mirro", "--prune-backups=zzz"]): + result = mirro.main() + + assert result == 1 + assert "Invalid value for --prune-backups" in capsys.readouterr().out