Add logic to handle editor specific flags dynamically, add --version flag, edit .gitignore, add tests
This commit is contained in:
287
tests/test_mirro.py
Normal file
287
tests/test_mirro.py
Normal file
@@ -0,0 +1,287 @@
|
||||
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"
|
||||
Reference in New Issue
Block a user