feat(sp25): cyclone recover-ingest CLI for stranded source files
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
"""SP25: integration tests for ``cyclone recover-ingest`` (parse + DB-write recovery).
|
||||
|
||||
Fixtures used (all real production EDI from the test fixture corpus):
|
||||
- ``co_medicaid_837p.txt`` — 1-claim 837P fixture (small, fast).
|
||||
- ``co_medicaid_835.txt`` — minimal 835 fixture (real CO Medicaid).
|
||||
|
||||
These tests do NOT cover the operator's full ingest/ batch — that's
|
||||
Task 6 in the plan and runs against the live DB after these unit
|
||||
tests are green.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from cyclone import db, store
|
||||
from cyclone.cli import main as cli_main
|
||||
from cyclone.db import Batch, Claim, ProcessedInboundFile, Remittance
|
||||
|
||||
FIX_837P = Path(__file__).parent / "fixtures" / "co_medicaid_837p.txt"
|
||||
FIX_835 = Path(__file__).parent / "fixtures" / "co_medicaid_835.txt"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner():
|
||||
return CliRunner()
|
||||
|
||||
|
||||
# --- helpers --------------------------------------------------------------
|
||||
|
||||
def _count_claims() -> int:
|
||||
s = db.SessionLocal()()
|
||||
try:
|
||||
return s.query(Claim).count()
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def _count_remittances() -> int:
|
||||
s = db.SessionLocal()()
|
||||
try:
|
||||
return s.query(Remittance).count()
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def _count_batches() -> int:
|
||||
s = db.SessionLocal()()
|
||||
try:
|
||||
return s.query(Batch).count()
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
# --- tests ----------------------------------------------------------------
|
||||
|
||||
def test_recover_ingest_837p_persists_claims(runner):
|
||||
"""One 837P ingest via the CLI lands a Batch + ≥1 Claim row + a dedup row."""
|
||||
pre_claims = _count_claims()
|
||||
pre_batches = _count_batches()
|
||||
result = runner.invoke(
|
||||
cli_main,
|
||||
["recover-ingest", "--file", str(FIX_837P),
|
||||
"--sftp-block-name", "test-recover-837p"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
assert result.exit_code == 0, result.stderr or result.stdout
|
||||
out = (result.stdout or "") + (result.stderr or "")
|
||||
assert "ok" in out
|
||||
assert FIX_837P.name in out
|
||||
|
||||
assert _count_claims() > pre_claims, "expected new Claim rows"
|
||||
assert _count_batches() > pre_batches, "expected new Batch row"
|
||||
|
||||
# Dedup row created.
|
||||
s = db.SessionLocal()()
|
||||
try:
|
||||
row = s.query(ProcessedInboundFile).filter_by(
|
||||
sftp_block_name="test-recover-837p", name=FIX_837P.name,
|
||||
).first()
|
||||
finally:
|
||||
s.close()
|
||||
assert row is not None, "expected processed_inbound_files row"
|
||||
assert row.status == "ok"
|
||||
assert row.file_type == "837p"
|
||||
|
||||
|
||||
def test_recover_ingest_835_persists_remittance(runner):
|
||||
"""One 835 ingest via the CLI lands a Batch + Remittance row + dedup row."""
|
||||
pre_batches = _count_batches()
|
||||
pre_remits = _count_remittances()
|
||||
result = runner.invoke(
|
||||
cli_main,
|
||||
["recover-ingest", "--file", str(FIX_835),
|
||||
"--sftp-block-name", "test-recover-835"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
assert result.exit_code == 0, result.stderr or result.stdout
|
||||
assert "ok" in (result.stdout or "") + (result.stderr or "")
|
||||
|
||||
assert _count_batches() > pre_batches
|
||||
assert _count_remittances() > pre_remits
|
||||
|
||||
s = db.SessionLocal()()
|
||||
try:
|
||||
row = s.query(ProcessedInboundFile).filter_by(
|
||||
sftp_block_name="test-recover-835", name=FIX_835.name,
|
||||
).first()
|
||||
finally:
|
||||
s.close()
|
||||
assert row is not None
|
||||
assert row.status == "ok"
|
||||
assert row.file_type == "835"
|
||||
assert row.claim_count >= 1
|
||||
|
||||
|
||||
def test_recover_ingest_is_idempotent(runner):
|
||||
"""Second invocation with the same (block, file) is a no-op (duplicate)."""
|
||||
# First call — must ingest.
|
||||
r1 = runner.invoke(
|
||||
cli_main,
|
||||
["recover-ingest", "--file", str(FIX_837P),
|
||||
"--sftp-block-name", "test-recover-idem"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
assert r1.exit_code == 0, r1.stderr or r1.stdout
|
||||
|
||||
pre_claims = _count_claims()
|
||||
pre_batches = _count_batches()
|
||||
# Second call — must skip.
|
||||
r2 = runner.invoke(
|
||||
cli_main,
|
||||
["recover-ingest", "--file", str(FIX_837P),
|
||||
"--sftp-block-name", "test-recover-idem"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
assert r2.exit_code == 0, r2.stderr or r2.stdout
|
||||
assert "skipped" in (r2.stdout or "") + (r2.stderr or "")
|
||||
|
||||
# No new rows on second call.
|
||||
assert _count_claims() == pre_claims
|
||||
assert _count_batches() == pre_batches
|
||||
|
||||
|
||||
def test_recover_ingest_missing_file_returns_failed(runner, tmp_path):
|
||||
"""Missing file → Click rejects with usage error (the CLI uses exists=True)."""
|
||||
missing = tmp_path / "does-not-exist.837"
|
||||
pre_claims = _count_claims()
|
||||
result = runner.invoke(
|
||||
cli_main,
|
||||
["recover-ingest", "--file", str(missing),
|
||||
"--sftp-block-name", "test-recover-missing"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
# Click rejects up-front; usage error exits with code 2.
|
||||
assert result.exit_code == 2
|
||||
out = (result.stdout or "") + (result.stderr or "")
|
||||
assert "does not exist" in out or "Invalid value" in out
|
||||
assert _count_claims() == pre_claims
|
||||
Reference in New Issue
Block a user