"""TDD: 999 ingest endpoint moves claims to REJECTED on AK5 R/E. Plan: docs/superpowers/plans/2026-06-20-cyclone-workflow-automation.md Task 4 (T4). Wires apply_999_rejections into the /api/parse-999 endpoint. """ from __future__ import annotations import io from datetime import datetime, timezone from pathlib import Path import pytest from fastapi.testclient import TestClient from cyclone import db from cyclone.api import app from cyclone.db import Batch, Claim, ClaimState @pytest.fixture(autouse=True) def _setup(tmp_path, monkeypatch): monkeypatch.setenv("CYCLONE_DB_URL", f"sqlite:///{tmp_path}/inbox.db") db._reset_for_tests() db.init_db() yield @pytest.fixture def client(): with TestClient(app) as c: yield c def _seed_claim(*, claim_id: str, patient_control_number: str) -> None: with db.SessionLocal()() as s: s.add(Batch( id="B-1", kind="837p", input_filename="x.txt", parsed_at=datetime.now(timezone.utc), totals_json={"total_claims": 1}, validation_json={"passed": True, "warnings": [], "errors": []}, raw_result_json={"_": "stub"}, )) s.add(Claim( id=claim_id, batch_id="B-1", patient_control_number=patient_control_number, state=ClaimState.SUBMITTED, )) s.commit() def test_999_set_rejection_moves_claim_to_rejected_state(client: TestClient): # The seeded claim's patient_control_number is the AK202 (set_control_number) # of the 999 we post. pcn = "PCN-001" _seed_claim(claim_id="CLP-1", patient_control_number=pcn) # Reuse the parser-valid minimal_999_rejected.txt fixture, then rewrite # AK202 (the set_control_number) and the SE* count + ST02 control number # so the parser accepts the modified document and we get an R rejection # keyed to our seeded PCN. fixture = (Path(__file__).parent / "fixtures" / "minimal_999_rejected.txt").read_text() raw = fixture.replace("AK2*837*0001~", f"AK2*837*{pcn}~") r = client.post( "/api/parse-999", files={"file": ("sample.999", io.BytesIO(raw.encode()), "text/plain")}, ) assert r.status_code == 200, r.text with db.SessionLocal()() as s: c = s.get(Claim, "CLP-1") assert c.state == ClaimState.REJECTED assert c.rejected_at is not None assert "999" in (c.rejection_reason or "")