lllin000_PaperForge/tests/cli/test_text_contracts.py

97 lines
3.7 KiB
Python
Raw Normal View History

"""CLI contract tests for text-output commands: sync, ocr, doctor, repair, setup."""
from __future__ import annotations
import pytest
class TestSyncCli:
"""Contract: 'paperforge sync' produces expected text output."""
def test_sync_dry_run_on_empty_vault(self, cli_invoker):
"""sync --dry-run on empty vault exits 0 with DRY-RUN message."""
result = cli_invoker(["sync", "--dry-run"])
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr}"
assert "DRY-RUN" in result.stdout
def test_sync_rejects_nonexistent_vault(self, cli_invoker):
"""sync on nonexistent vault exits non-zero."""
result = cli_invoker(["sync", "--vault", "/nonexistent/path"])
assert result.returncode != 0
class TestOcrCli:
"""Contract: 'paperforge ocr' produces expected output."""
def test_ocr_diagnose_on_empty_vault(self, cli_invoker):
"""ocr --diagnose on empty vault produces doctor output."""
result = cli_invoker(["ocr", "--diagnose"])
# May exit 0 or 1 depending on env, but must not crash
assert "OCR Doctor" in result.stdout
def test_ocr_invalid_args(self, cli_invoker):
"""ocr with invalid args should exit non-zero."""
result = cli_invoker(["--invalid-flag"])
assert result.returncode != 0
class TestDoctorCli:
"""Contract: 'paperforge doctor' produces verdict output."""
def test_doctor_on_empty_vault(self, cli_invoker):
"""doctor runs on empty vault without crashing."""
result = cli_invoker(["doctor"])
# doctor should exit 0 even when things are missing (it reports, doesn't fail)
output = result.stdout + result.stderr
assert len(output) > 0, "doctor produced no output"
def test_doctor_output_has_verdict_pattern(self, cli_invoker):
"""doctor output contains [OK], [WARN], or [FAIL] patterns."""
result = cli_invoker(["doctor"])
markers = ["[OK]", "[WARN]", "[FAIL]"]
has_marker = any(m in result.stdout for m in markers)
# On empty vault, doctor may not have markers — just verify no crash
if not has_marker:
pytest.skip(
"Doctor did not produce verdict markers (may depend on vault state)"
)
def test_doctor_on_nonexistent_vault(self, cli_invoker):
"""doctor on nonexistent vault exits non-zero."""
result = cli_invoker(["doctor", "--vault", "/nonexistent/path"])
assert result.returncode != 0
class TestRepairCli:
"""Contract: 'paperforge repair' produces repair scan output."""
def test_repair_on_empty_vault(self, cli_invoker):
"""repair on empty vault exits 0 with no divergences."""
result = cli_invoker(["repair"])
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr}"
def test_repair_dry_run_no_modify(self, cli_invoker):
"""repair without --fix does not modify vault."""
result = cli_invoker(["repair"])
assert result.returncode == 0
class TestSetupCli:
"""Contract: 'paperforge setup' produces setup output."""
def test_setup_headless_with_skip_checks(self, cli_invoker):
"""setup --headless --skip-checks runs without crashing."""
result = cli_invoker(
[
"setup", "--headless", "--skip-checks",
"--zotero-data", "/tmp/mock_zotero",
]
)
# May exit 0 or non-zero depending on environment, but must produce output
assert len(result.stdout) > 0 or len(result.stderr) > 0
def test_setup_no_args_shows_usage(self, cli_invoker):
"""setup without --headless shows help message."""
result = cli_invoker(["setup"])
assert len(result.stdout) > 0 or len(result.stderr) > 0