"""Tests for the `parse-835` Click subcommand.""" import json from pathlib import Path import pytest from click.testing import CliRunner from cyclone.cli import main FIXTURE = Path(__file__).parent / "fixtures" / "minimal_835.txt" UNBALANCED = Path(__file__).parent / "fixtures" / "unbalanced_835.txt" CO_FIXTURE = Path(__file__).parent / "fixtures" / "co_medicaid_835.txt" def test_cli_parse_835_writes_outputs(tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["parse-835", str(FIXTURE), "--output-dir", str(tmp_path)]) assert result.exit_code == 0, result.output written = list(tmp_path.glob("payout-*.json")) assert len(written) == 1 summary = json.loads((tmp_path / "summary.json").read_text()) assert summary["total_claims"] == 1 assert summary["passed"] == 1 assert summary["failed"] == 0 def test_cli_parse_835_strict_promotes_warnings(tmp_path: Path): runner = CliRunner() # Strict mode: even warnings cause failed=1. Minimal fixture is fully # passing so this is a smoke test that the flag is wired. result = runner.invoke(main, ["parse-835", str(FIXTURE), "--output-dir", str(tmp_path), "--strict"]) assert result.exit_code == 0 summary = json.loads((tmp_path / "summary.json").read_text()) assert summary["passed"] == 1 def test_cli_parse_835_no_raw_segments(tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["parse-835", str(FIXTURE), "--output-dir", str(tmp_path), "--no-raw-segments"]) assert result.exit_code == 0 data = json.loads(next(tmp_path.glob("payout-*.json")).read_text()) # The claim lives under the "claim" key in each payout file. assert data["claim"]["raw_segments"] == [] def test_cli_parse_835_unbalanced_strict_fails(tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["parse-835", str(UNBALANCED), "--output-dir", str(tmp_path), "--strict"]) assert result.exit_code == 0, result.output summary = json.loads((tmp_path / "summary.json").read_text()) assert summary["passed"] == 0 assert summary["failed"] == 1 # R835_BAL_BPR_vs_CLP04 is the rule that fired. assert "R835_BAL_BPR_vs_CLP04" in summary["issues_by_rule"] def test_cli_parse_835_unknown_payer_errors(tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["parse-835", str(FIXTURE), "--output-dir", str(tmp_path), "--payer", "no_such_payer"]) assert result.exit_code != 0 def test_cli_parse_835_missing_file_exits_2(tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["parse-835", "/no/such/file.txt", "--output-dir", str(tmp_path)]) assert result.exit_code == 2 def test_cli_parse_835_generic_payer(tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["parse-835", str(FIXTURE), "--output-dir", str(tmp_path), "--payer", "generic_835"]) assert result.exit_code == 0, result.output summary = json.loads((tmp_path / "summary.json").read_text()) assert summary["passed"] == 1 def test_cli_parse_835_summary_counts_consistent(tmp_path: Path): """Regression: passed + failed must equal total_claims for any batch size. The CO Medicaid fixture has 2 claim payments; if the batch validates cleanly, both must show as passed (passed=2, failed=0). A pre-fix implementation set passed=1 regardless of claim count. """ runner = CliRunner() result = runner.invoke(main, ["parse-835", str(CO_FIXTURE), "--output-dir", str(tmp_path)]) assert result.exit_code == 0, result.output summary = json.loads((tmp_path / "summary.json").read_text()) assert summary["total_claims"] == 2 assert summary["passed"] + summary["failed"] == summary["total_claims"] assert summary["passed"] == 2 assert summary["failed"] == 0 assert summary["failed_claim_ids"] == []