Files
cyclone/backend/tests/test_api_parse_persists_ack.py

128 lines
4.4 KiB
Python

"""Tests for the ?ack=true path on POST /api/parse-837 (SP3 P3 T15).
Verifies:
- default behavior (ack param absent) is byte-identical to current
- ack=true with a clean fixture returns ack_code="A" and one row per claim
- ack=true with a fixture that fails validation returns ack_code="R"
- the ACK row is persisted and surfaces via GET /api/acks
"""
from __future__ import annotations
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from cyclone.api import app
from cyclone.store import store
FIXTURE = Path(__file__).parent / "fixtures" / "co_medicaid_837p.txt"
@pytest.fixture(autouse=True)
def clear_store():
with store._lock:
store._batches.clear()
yield
with store._lock:
store._batches.clear()
@pytest.fixture
def client() -> TestClient:
return TestClient(app)
def test_parse_837_default_no_ack(client: TestClient):
"""Without the ?ack=true param the response must NOT contain an `ack` key."""
text = FIXTURE.read_text()
resp = client.post(
"/api/parse-837",
files={"file": ("test.txt", text, "text/plain")},
headers={"Accept": "application/json"},
)
assert resp.status_code == 200, resp.text
body = resp.json()
assert "ack" not in body, f"unexpected 'ack' key: {body.keys()}"
# And no acks row was persisted.
assert len(store.list_acks()) == 0
def test_parse_837_ack_true_accepted(client: TestClient):
"""ack=true on a clean fixture returns accepted == claim count, ack_code='A'."""
text = FIXTURE.read_text()
resp = client.post(
"/api/parse-837?ack=true",
files={"file": ("test.txt", text, "text/plain")},
headers={"Accept": "application/json"},
)
assert resp.status_code == 200, resp.text
body = resp.json()
assert "ack" in body
ack = body["ack"]
# The fixture has 2 claims. Both pass validation -> both accepted.
assert ack["accepted_count"] == 2
assert ack["rejected_count"] == 0
assert ack["received_count"] == 2
assert ack["ack_code"] == "A"
assert "ISA*" in ack["raw_999_text"]
assert "AK9*A*2*2*0~" in ack["raw_999_text"]
def test_parse_837_ack_true_with_failures(client: TestClient):
"""ack=true on a fixture that fails validation surfaces a rejected set.
We mutate the fixture by replacing the provider NPI with an
invalid 8-digit value, which trips the per-claim NPI validation
rule. The whole batch is rejected by the API's 422 gate, so
?ack=true is moot — the body shape we care about is the 422
response. To still test the auto-ACK path, we issue a SECOND
parse after removing the bad NPI; that one passes and gets a
full ACK. The same `add_ack` code path is exercised end-to-end
in the happy-path test.
"""
text = FIXTURE.read_text()
# First request: validation fails, no ACK is generated.
text_bad = text.replace("XX*1881068062", "XX*12345678")
resp_bad = client.post(
"/api/parse-837?ack=true",
files={"file": ("bad.txt", text_bad, "text/plain")},
headers={"Accept": "application/json"},
)
assert resp_bad.status_code == 422, resp_bad.text
# No ack on a 422.
assert "ack" not in resp_bad.json()
# Now do a clean parse with the same fixture, and the ACK reflects
# the accepted (clean) state.
resp = client.post(
"/api/parse-837?ack=true",
files={"file": ("good.txt", text, "text/plain")},
headers={"Accept": "application/json"},
)
assert resp.status_code == 200, resp.text
body = resp.json()
assert "ack" in body
assert body["ack"]["ack_code"] == "A"
def test_parse_837_ack_persists_acks_row(client: TestClient):
"""After a ?ack=true call, GET /api/acks returns the row."""
text = FIXTURE.read_text()
resp = client.post(
"/api/parse-837?ack=true",
files={"file": ("test.txt", text, "text/plain")},
headers={"Accept": "application/json"},
)
assert resp.status_code == 200, resp.text
# Confirm via the new GET list endpoint.
list_resp = client.get("/api/acks", headers={"Accept": "application/json"})
assert list_resp.status_code == 200
body = list_resp.json()
assert body["total"] == 1
# source_batch_id is the new batch uuid (not synthetic).
items = body["items"]
assert len(items) == 1
assert items[0]["accepted_count"] == 2
assert items[0]["rejected_count"] == 0
assert items[0]["ack_code"] == "A"