"""Tests for src/cmos/cli.py — end-to-end draft reformatting. The CLI orchestrates parser → formatter → reassemble. Tests inject a fake formatter so no API calls happen; the goal is to pin the glue logic, not the LLM behavior. """ import subprocess import sys from pathlib import Path import pytest from cmos.cli import reformat_draft from cmos.parser import NoBibliographyError def _fake_formatter(messy: str) -> str: # Deterministic fake: return a cleaned-up marker string so the output is # recognizable. return f"FORMATTED({messy.strip()})" def test_reformat_draft_rewrites_only_bibliography_section(): draft = """\ # My Paper Some prose with citations. ## Bibliography yu, charles. interior chinatown. 2020. kwon, hyeyoung. inclusion work. 2022. """ output = reformat_draft(draft, formatter=_fake_formatter) assert "# My Paper" in output assert "Some prose with citations." in output assert "FORMATTED(yu, charles. interior chinatown. 2020.)" in output assert "FORMATTED(kwon, hyeyoung. inclusion work. 2022.)" in output # Original messy lines must not also survive. assert "yu, charles. interior chinatown. 2020." not in output.replace( "FORMATTED(yu, charles. interior chinatown. 2020.)", "" ) def test_reformat_draft_preserves_sections_after_bibliography(): draft = """\ ## Bibliography one entry. ## Appendix appendix text. """ output = reformat_draft(draft, formatter=_fake_formatter) assert "FORMATTED(one entry.)" in output assert "## Appendix" in output assert "appendix text." in output def test_reformat_draft_raises_when_no_bibliography(): with pytest.raises(NoBibliographyError): reformat_draft("## Intro\n\nno bib here.\n", formatter=_fake_formatter) def test_reformat_draft_bibliography_heading_preserved(): draft = "## Bibliography\n\nentry.\n" output = reformat_draft(draft, formatter=_fake_formatter) assert "## Bibliography" in output def test_python_dash_m_invocation_actually_runs_main(): """Regression test: `python -m cmos.cli` must actually invoke main(). Without an `if __name__ == "__main__"` guard at the bottom of cli.py, `python -m cmos.cli` imports the module body but never calls main(), so the process silently exits 0 with empty stdout. That looks indistinguishable from a successful run that produced no output — the worst kind of bug, since callers assume the pipeline ran. This test forces the guard to exist by invoking the CLI as a subprocess with --help and asserting argparse actually fired. """ result = subprocess.run( [sys.executable, "-m", "cmos.cli", "--help"], capture_output=True, text=True, timeout=10, ) # argparse --help exits 0 whether or not main() ran, so the # discriminator is the stdout content. Without main(), stdout is empty. assert result.returncode == 0, ( f"expected exit 0, got {result.returncode}; stderr={result.stderr!r}" ) assert result.stdout, ( "stdout was empty — `python -m cmos.cli` likely silently exited " "without invoking main(). Check that cli.py has an " '`if __name__ == "__main__": sys.exit(main())` guard at the bottom.' ) assert "format" in result.stdout assert "usage" in result.stdout.lower() def test_reformat_draft_preserves_order_under_concurrency(): # With concurrent execution the formatter is called on all entries in # parallel; the CLI must reassemble them in input order regardless of # which call finishes first. This test uses a fake formatter that # sleeps based on the entry content so earlier entries finish AFTER # later ones if order were naively tied to completion. import time def slow_fake(messy: str) -> str: # Entries with lower index sleep longer so they finish last. n = int(messy.split()[-1]) time.sleep(0.05 * (5 - n)) return f"FORMATTED({messy})" draft = """\ ## Bibliography entry 0 entry 1 entry 2 entry 3 entry 4 """ output = reformat_draft(draft, formatter=slow_fake, concurrency=4) # Entries must appear in input order. lines = [l for l in output.splitlines() if l.startswith("FORMATTED(")] assert lines == [ "FORMATTED(entry 0)", "FORMATTED(entry 1)", "FORMATTED(entry 2)", "FORMATTED(entry 3)", "FORMATTED(entry 4)", ]