dd7da18279
The dzinesco SP9 seed had `paths.inbound` and `paths.outbound` mapped to
the wrong Gainwell MFT directories:
- paths.outbound was FromHPE/ (HPE sends files FROM here TO us — inbound)
- paths.inbound was ToHPE/ (we send files TO here — outbound)
So `/api/clearhouse/submit` was writing 837P claims to FromHPE (where
HPE puts acks/835s) and the SP16 scheduler was polling ToHPE (where our
claims go). 999 / TA1 / 835 files in the real FromHPE inbox were
unreachable.
Semantics per operator (2026-06-24):
- FromHPE = HPE/Gainwell → us = 999, TA1, 835 (inbound)
- ToHPE = us → HPE/Gainwell = 837P claims (outbound)
**Runtime code (the actual fix):**
- backend/src/cyclone/store.py — SP9 seed paths flipped
- backend/src/cyclone/edi/filenames.py — docstring corrected
**Test fixtures + assertions (would otherwise fail on the new seed):**
- backend/tests/test_clearhouse_api.py
- backend/tests/test_providers_seed.py
- backend/tests/test_sftp_stub.py — incl. inbound dir paths
- backend/tests/test_sftp_paramiko.py
- backend/tests/test_store_update_clearhouse.py
- backend/tests/test_api_clearhouse_patch.py
- backend/tests/test_scheduler.py
- backend/tests/test_api_scheduler.py
**Docs (text-only — keeps the codebase self-consistent):**
- README.md
- docs/reference/co-medicaid.md
- docs/superpowers/specs/2026-06-20-cyclone-multi-payer-npi-sftp-design.md
**Operator action required after merge:** the existing clearhouse row in
`~/.local/share/cyclone/cyclone.db` was seeded with the old wrong
paths. Easiest recovery:
1. `rm ~/.local/share/cyclone/cyclone.db` and let `ensure_clearhouse_seeded` re-run on next boot, OR
2. PATCH /api/clearhouse with the new `paths` block (the SP25
reconfigure hook picks it up live, including by the running scheduler).
**Verification:**
- 82 tests in the affected files pass (test_clearhouse_api, test_providers_seed,
test_sftp_stub, test_sftp_paramiko, test_store_update_clearhouse,
test_api_clearhouse_patch, test_scheduler, test_api_scheduler, test_filenames).
- Full backend suite: 1029 pass + 36 pre-existing order-dependent flakes
unrelated to this change (verified by running the same tests in isolation).
104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
"""SP9 — clearhouse API endpoint tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from cyclone import db as db_mod
|
|
from cyclone.api import app
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CYCLONE_DB_URL", f"sqlite:///{tmp_path}/test.db")
|
|
db_mod._reset_for_tests()
|
|
db_mod.init_db()
|
|
with TestClient(app) as c:
|
|
yield c
|
|
db_mod._reset_for_tests()
|
|
|
|
|
|
def test_get_clearhouse_seeded(client):
|
|
# Lifespan runs ensure_clearhouse_seeded()
|
|
r = client.get("/api/clearhouse")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["name"] == "dzinesco"
|
|
assert body["tpid"] == "11525703"
|
|
assert body["sftp_block"]["stub"] is True
|
|
assert "ToHPE" in body["sftp_block"]["paths"]["outbound"]
|
|
assert "FromHPE" in body["sftp_block"]["paths"]["inbound"]
|
|
|
|
|
|
def test_list_providers(client):
|
|
r = client.get("/api/config/providers")
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert {p["label"] for p in body} == {"Montrose", "Delta", "Salida"}
|
|
assert {p["npi"] for p in body} == {"1881068062", "1851446637", "1467507269"}
|
|
|
|
|
|
def test_get_provider_by_npi(client):
|
|
r = client.get("/api/config/providers/1881068062")
|
|
assert r.status_code == 200
|
|
assert r.json()["label"] == "Montrose"
|
|
|
|
|
|
def test_get_provider_404(client):
|
|
r = client.get("/api/config/providers/9999999999")
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_list_payers(client):
|
|
r = client.get("/api/config/payers")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert len(body) == 1
|
|
assert body[0]["payer_id"] == "CO_TXIX"
|
|
|
|
|
|
def test_list_payer_configs_for_co_txix(client):
|
|
r = client.get("/api/config/payers/CO_TXIX/configs")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
# The lifespan loads config/payers.yaml; configs come from the registry
|
|
tx_types = {c["transaction_type"] for c in body}
|
|
assert "837P" in tx_types or "835" in tx_types
|
|
|
|
|
|
def test_reload_config(client):
|
|
r = client.post("/api/admin/reload-config")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["ok"] is True
|
|
assert isinstance(body["loaded"], int)
|
|
|
|
|
|
def test_submit_clearhouse_rejects_empty_claim_ids(client):
|
|
r = client.post("/api/clearhouse/submit", json={"claim_ids": [], "payer_id": "CO_TXIX"})
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_submit_clearhouse_rejects_missing_payer_id(client):
|
|
r = client.post("/api/clearhouse/submit", json={"claim_ids": ["X"]})
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_submit_clearhouse_handles_unknown_claim_id(client):
|
|
r = client.post("/api/clearhouse/submit", json={
|
|
"claim_ids": ["DOES_NOT_EXIST"],
|
|
"payer_id": "CO_TXIX",
|
|
})
|
|
# 200 with per-claim ok:false entries (graceful degradation)
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["ok"] is True
|
|
assert body["stub"] is True
|
|
assert len(body["submitted"]) == 1
|
|
assert body["submitted"][0]["ok"] is False
|
|
assert "not found" in body["submitted"][0]["error"] or "cannot be re-serialized" in body["submitted"][0]["error"]
|