Files
mirro/tests/test_mirro.py
2025-11-13 16:25:10 +00:00

301 lines
8.2 KiB
Python

import os
import time
import subprocess
from pathlib import Path
from unittest.mock import patch
import pytest
import mirro.main as mirro
# ============================================================
# get_version
# ============================================================
def test_get_version_found(monkeypatch):
monkeypatch.setattr(mirro.importlib.metadata, "version", lambda _: "1.2.3")
assert mirro.get_version() == "1.2.3"
def test_get_version_not_found(monkeypatch):
def raiser(_):
raise mirro.importlib.metadata.PackageNotFoundError
monkeypatch.setattr(mirro.importlib.metadata, "version", raiser)
assert mirro.get_version() == "unknown"
# ============================================================
# read_file / write_file
# ============================================================
def test_read_file_exists(tmp_path):
p = tmp_path / "x.txt"
p.write_text("hello\n", encoding="utf-8")
assert mirro.read_file(p) == "hello\n"
def test_read_file_missing(tmp_path):
assert mirro.read_file(tmp_path / "nope.txt") == ""
def test_write_file(tmp_path):
p = tmp_path / "y.txt"
mirro.write_file(p, "data")
assert p.read_text(encoding="utf-8") == "data"
# ============================================================
# backup_original
# ============================================================
def test_backup_original(tmp_path, monkeypatch):
original_path = tmp_path / "test.txt"
original_content = "ABC"
backup_dir = tmp_path / "backups"
# Freeze timestamps
monkeypatch.setattr(
time, "gmtime", lambda: time.struct_time((2023, 1, 2, 3, 4, 5, 0, 0, 0))
)
monkeypatch.setattr(
time,
"strftime",
lambda fmt, _: {
"%Y-%m-%d %H:%M:%S UTC": "2023-01-02 03:04:05 UTC",
"%Y%m%dT%H%M%S": "20230102T030405",
}[fmt],
)
backup_path = mirro.backup_original(original_path, original_content, backup_dir)
assert backup_path.exists()
text = backup_path.read_text(encoding="utf-8")
assert "mirro backup" in text
assert "Original file:" in text
assert original_content in text
# ============================================================
# Helper to run main()
# ============================================================
def simulate_main(
monkeypatch,
capsys,
args,
*,
editor="nano",
start_content=None,
edited_content=None,
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:
temp.write_text(start_content or "", encoding="utf-8")
else:
temp.write_text(edited_content, encoding="utf-8")
return 0
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)
target.write_text(start_content or "", encoding="utf-8")
with patch("sys.argv", ["mirro"] + args):
result = mirro.main()
out = capsys.readouterr().out
return result, out
# ============================================================
# main: missing file argument
# ============================================================
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)
# ============================================================
def test_main_existing_unchanged(tmp_path, monkeypatch, capsys):
target = tmp_path / "file.txt"
target.write_text("hello\n", encoding="utf-8")
def fake_call(cmd):
temp = Path(cmd[-1])
temp.write_text("hello\n", encoding="utf-8")
monkeypatch.setenv("EDITOR", "nano")
monkeypatch.setattr(subprocess, "call", fake_call)
monkeypatch.setattr(os, "access", lambda p, m: True)
with patch("sys.argv", ["mirro", str(target)]):
mirro.main()
out = capsys.readouterr().out
assert "file hasn't changed" in out
# ============================================================
# main: changed file
# ============================================================
def test_main_existing_changed(tmp_path, monkeypatch, capsys):
target = tmp_path / "file2.txt"
result, out = simulate_main(
monkeypatch,
capsys,
args=[str(target)],
start_content="old\n",
edited_content="new\n",
file_exists=True,
)
assert "file changed; original backed up at" in out
assert target.read_text(encoding="utf-8") == "new\n"
# ============================================================
# main: new file unchanged
# ============================================================
def test_main_new_file_unchanged(tmp_path, monkeypatch, capsys):
new = tmp_path / "new.txt"
result, out = simulate_main(
monkeypatch,
capsys,
args=[str(new)],
start_content=None,
edited_content="This is a new file created with 'mirro'!\n",
file_exists=False,
)
assert "file hasn't changed" in out
assert not new.exists()
# ============================================================
# main: new file changed
# ============================================================
def test_main_new_file_changed(tmp_path, monkeypatch, capsys):
new = tmp_path / "new2.txt"
result, out = simulate_main(
monkeypatch,
capsys,
args=[str(new)],
start_content=None,
edited_content="XYZ\n",
file_exists=False,
)
assert "file changed; original backed up at" in out
assert new.read_text(encoding="utf-8") == "XYZ\n"
# ============================================================
# main: permission denied for existing file (line 78)
# ============================================================
def test_main_permission_denied_existing(tmp_path, monkeypatch, capsys):
target = tmp_path / "blocked.txt"
target.write_text("hello", encoding="utf-8")
monkeypatch.setenv("EDITOR", "nano")
monkeypatch.setattr(os, "access", lambda p, m: False)
with patch("sys.argv", ["mirro", str(target)]):
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)
# ============================================================
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)
# Directory is not writable
def fake_access(path, mode):
if path == parent:
return False
return True
monkeypatch.setattr(os, "access", fake_access)
monkeypatch.setenv("EDITOR", "nano")
with patch("sys.argv", ["mirro", str(newfile)]):
result = mirro.main()
out = capsys.readouterr().out
assert "Need elevated privileges to create" in out
assert result == 1
# ============================================================
# main: non-nano editor (ordering branch)
# ============================================================
def test_main_editor_non_nano(tmp_path, monkeypatch, capsys):
target = tmp_path / "vim.txt"
target.write_text("old\n", encoding="utf-8")
def fake_call(cmd):
temp = Path(cmd[1]) # in non-nano mode
temp.write_text("edited\n", encoding="utf-8")
monkeypatch.setenv("EDITOR", "vim")
monkeypatch.setattr(subprocess, "call", fake_call)
monkeypatch.setattr(os, "access", lambda p, m: True)
with patch("sys.argv", ["mirro", str(target)]):
mirro.main()
assert target.read_text(encoding="utf-8") == "edited\n"