9bca4b608a
Backend:
- New POST /api/batches/{id}/export-837: regenerate X12 837 files
for a list of claim_ids into a ZIP using HCPF file naming standards,
with a unique interchange/group control number per export. Wire
the clearhouse Loop 1000A (NM1*41 + PER) and per-payer receiver
(NM1*40) blocks so the serializer no longer falls back to
CYCLONE / RECEIVER placeholders.
- /api/parse-837 and /api/parse-835 now surface the server-side
batch_id in both JSON and NDJSON response shapes so the frontend
can hit batch-scoped endpoints without an extra listBatches
round-trip.
- Filename helpers and the 837 serializer updated to match the new
HCPF envelope; tests cover batch export, parse batch_id, and the
serializer's control-number uniqueness guarantee.
Frontend:
- New shared components: ClaimCard, ClaimCard837, DominantKpiCard,
EditorialNote, ExportBar, TickerTape, and a charts/ set
(BarChart, HBarChart, SegmentedBar, AgingBars).
- New useBatchExport hook driving ExportBar's download flow against
the new endpoint.
- ClaimDrawer, Lane, and Layout migrated from raw CSS-variable
colors to Tailwind theme tokens (bg-card, text-foreground,
border/60, etc.) for consistency with the rest of the instrument
chrome; the active tab indicator gains a subtle accent glow.
- Upload, Inbox, Batches, BatchDiff, Reconciliation, and Acks pages
reworked to compose the new shared components and consume the new
batch-scoped API surface (notably ExportBar wired into Batches).
Tooling / Docs:
- Add audit-uiux.mjs and a docs/goodclaim.x12 sample fixture.
- Update ClaimDrawer testids and add coverage for the new
components and the useBatchExport hook.
Rolls up into the v0.2.0 release tag.
95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""SP9 — SFTP stub tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from cyclone.clearhouse import SftpClient
|
|
from cyclone.providers import SftpBlock
|
|
|
|
|
|
@pytest.fixture
|
|
def sftp_block(tmp_path):
|
|
staging = tmp_path / "staging"
|
|
return SftpBlock(
|
|
host="mft.gainwelltechnologies.com",
|
|
port=22,
|
|
username="colorado-fts\\coxix_prod_11525703",
|
|
paths={
|
|
"outbound": "/CO XIX/PROD/coxix_prod_11525703/FromHPE",
|
|
"inbound": "/CO XIX/PROD/coxix_prod_11525703/ToHPE",
|
|
},
|
|
stub=True,
|
|
staging_dir=str(staging),
|
|
poll_seconds=300,
|
|
auth={"method": "keychain", "secret_ref": "sftp.gainwell.password"},
|
|
)
|
|
|
|
|
|
def test_stub_writes_preserving_remote_path(sftp_block, tmp_path):
|
|
client = SftpClient(sftp_block)
|
|
remote = "/CO XIX/PROD/coxix_prod_11525703/FromHPE/tp11525703-837P-20260620132243505-1of1.x12"
|
|
target = client.write_file(remote, b"ISA*00*...~IEA*1*1~")
|
|
assert target.exists()
|
|
assert target.read_bytes() == b"ISA*00*...~IEA*1*1~"
|
|
# Confirm the full nested MFT path is preserved under staging
|
|
rel = target.relative_to(sftp_block.staging_dir)
|
|
assert str(rel) == "CO XIX/PROD/coxix_prod_11525703/FromHPE/tp11525703-837P-20260620132243505-1of1.x12"
|
|
|
|
|
|
def test_stub_creates_parent_dirs(sftp_block):
|
|
client = SftpClient(sftp_block)
|
|
remote = "/deep/nested/path/file.x12"
|
|
target = client.write_file(remote, b"data")
|
|
assert target.exists()
|
|
assert target.parent.is_dir()
|
|
|
|
|
|
def test_stub_list_inbound_empty_when_no_local_files(sftp_block):
|
|
client = SftpClient(sftp_block)
|
|
assert client.list_inbound() == []
|
|
|
|
|
|
def test_stub_list_inbound_returns_local_files(sftp_block):
|
|
# Simulate operator dropping a file in the inbound staging dir
|
|
inbound_dir = Path(sftp_block.staging_dir) / "CO XIX/PROD/coxix_prod_11525703/ToHPE"
|
|
inbound_dir.mkdir(parents=True, exist_ok=True)
|
|
(inbound_dir / "TP11525703-837P_M019048402-20260520231513488-1of1_999.x12").write_bytes(b"X")
|
|
client = SftpClient(sftp_block)
|
|
files = client.list_inbound()
|
|
assert len(files) == 1
|
|
assert files[0].name.startswith("TP11525703-837P_M019048402")
|
|
assert files[0].size == 1
|
|
|
|
|
|
def test_stub_get_secret_returns_stub_when_keychain_empty(sftp_block):
|
|
client = SftpClient(sftp_block)
|
|
# Keychain is empty on the test box, so we get the stub sentinel
|
|
secret = client.get_secret("sftp.gainwell.password")
|
|
assert secret == "<stub-secret>"
|
|
|
|
|
|
def test_stub_read_file_returns_bytes(sftp_block, tmp_path):
|
|
"""SP16: the stub read_file returns bytes from the staging dir.
|
|
|
|
Lets the inbound scheduler exercise the same code path on a
|
|
workstation without a real MFT connection.
|
|
"""
|
|
inbound = tmp_path / "staging" / "ToHPE"
|
|
inbound.mkdir(parents=True)
|
|
(inbound / "TP11525703-837P_M019048402-20260618130000000-1of1_TA1.x12").write_bytes(
|
|
b"hello-world",
|
|
)
|
|
client = SftpClient(sftp_block)
|
|
body = client.read_file("/ToHPE/TP11525703-837P_M019048402-20260618130000000-1of1_TA1.x12")
|
|
assert body == b"hello-world"
|
|
|
|
|
|
def test_stub_read_file_missing_raises(sftp_block):
|
|
client = SftpClient(sftp_block)
|
|
with pytest.raises(FileNotFoundError):
|
|
client.read_file("/ToHPE/does-not-exist.x12")
|