196 lines
6.8 KiB
Python
196 lines
6.8 KiB
Python
"""Tests for the moved-to-store UI serializers for 999 / TA1 / 277CA acks.
|
|
|
|
The serializers were previously inlined in ``api_routers/acks.py``,
|
|
``api_routers/ta1_acks.py``, and ``api.py`` (`_277ca_to_ui`). SP25
|
|
moves them to ``cyclone.store.ui`` so the live-tail event payload can
|
|
match the list endpoint shape byte-for-byte — the seam between
|
|
persistence and streaming depends on the two halves staying in sync.
|
|
|
|
Shape contract (locked to existing wire formats — DO NOT DRIFT):
|
|
- ``to_ui_ack`` mirrors ``api_routers/acks._ack_to_ui`` exactly
|
|
- ``to_ui_ta1_ack`` mirrors ``api_routers/ta1_acks._ta1_to_ui`` exactly
|
|
- ``to_ui_two77ca_ack`` mirrors ``api._277ca_to_ui`` exactly
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, timezone
|
|
|
|
import pytest
|
|
|
|
from cyclone import db
|
|
from cyclone.store.ui import to_ui_ack, to_ui_ta1_ack, to_ui_two77ca_ack
|
|
|
|
|
|
@pytest.fixture
|
|
def ack_row() -> db.Ack:
|
|
"""Insert a 999 ack row and return it."""
|
|
with db.SessionLocal()() as s:
|
|
row = db.Ack(
|
|
source_batch_id="999-TEST-1",
|
|
accepted_count=3,
|
|
rejected_count=1,
|
|
received_count=4,
|
|
ack_code="P",
|
|
parsed_at=datetime(2026, 7, 2, 12, 0, tzinfo=timezone.utc),
|
|
raw_json={
|
|
"envelope": {"control_number": "000000001"},
|
|
"set_responses": [
|
|
{"set_control_number": "PCN-42"},
|
|
],
|
|
},
|
|
)
|
|
s.add(row)
|
|
s.commit()
|
|
s.refresh(row)
|
|
return row
|
|
|
|
|
|
@pytest.fixture
|
|
def ta1_row() -> db.Ta1Ack:
|
|
"""Insert a TA1 ack row and return it."""
|
|
with db.SessionLocal()() as s:
|
|
row = db.Ta1Ack(
|
|
source_batch_id="TA1-TEST-1",
|
|
control_number="000000007",
|
|
interchange_date=date(2026, 7, 2),
|
|
interchange_time="1200",
|
|
ack_code="A",
|
|
note_code="000",
|
|
ack_generated_date=None,
|
|
sender_id="SENDER-1",
|
|
receiver_id="RECEIVER-1",
|
|
parsed_at=datetime(2026, 7, 2, 12, 0, tzinfo=timezone.utc),
|
|
raw_json={"envelope": {"control_number": "000000007"}},
|
|
)
|
|
s.add(row)
|
|
s.commit()
|
|
s.refresh(row)
|
|
return row
|
|
|
|
|
|
@pytest.fixture
|
|
def two77ca_row() -> db.Two77caAck:
|
|
"""Insert a 277CA ack row and return it."""
|
|
with db.SessionLocal()() as s:
|
|
row = db.Two77caAck(
|
|
source_batch_id="277CA-TEST-1",
|
|
control_number="000000011",
|
|
accepted_count=2,
|
|
rejected_count=1,
|
|
paid_count=0,
|
|
pended_count=4,
|
|
parsed_at=datetime(2026, 7, 2, 12, 0, tzinfo=timezone.utc),
|
|
raw_json={"envelope": {"control_number": "000000011"}, "claim_statuses": []},
|
|
)
|
|
s.add(row)
|
|
s.commit()
|
|
s.refresh(row)
|
|
return row
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# to_ui_ack (999)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_to_ui_ack_round_trips_999_fields(ack_row):
|
|
"""to_ui_ack must produce the exact ``_ack_to_ui`` wire shape."""
|
|
body = to_ui_ack(ack_row)
|
|
assert body["id"] == ack_row.id
|
|
assert body["source_batch_id"] == "999-TEST-1"
|
|
assert body["accepted_count"] == 3
|
|
assert body["rejected_count"] == 1
|
|
assert body["received_count"] == 4
|
|
assert body["ack_code"] == "P"
|
|
# SQLite strips tzinfo from DateTime(timezone=True) values — the
|
|
# original `_ack_to_ui` serializer therefore produces a tz-naive
|
|
# ISO string in tests. Preserve the byte-for-byte format.
|
|
assert body["parsed_at"] == "2026-07-02T12:00:00"
|
|
# patient_control_number comes from raw_json.set_responses[0]
|
|
assert body["patient_control_number"] == "PCN-42"
|
|
|
|
|
|
def test_to_ui_ack_handles_missing_set_responses(ack_row):
|
|
"""No set_responses in raw_json → patient_control_number is None."""
|
|
with db.SessionLocal()() as s:
|
|
row = db.Ack(
|
|
source_batch_id="999-TEST-NULL",
|
|
accepted_count=0,
|
|
rejected_count=0,
|
|
received_count=0,
|
|
ack_code="A",
|
|
parsed_at=datetime(2026, 7, 2, 12, 0, tzinfo=timezone.utc),
|
|
raw_json={"envelope": {}, "set_responses": []},
|
|
)
|
|
s.add(row)
|
|
s.commit()
|
|
fresh = row
|
|
body = to_ui_ack(fresh)
|
|
assert body["patient_control_number"] is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# to_ui_ta1_ack (TA1)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_to_ui_ta1_ack_round_trips_fields(ta1_row):
|
|
"""to_ui_ta1_ack must produce the exact ``_ta1_to_ui`` wire shape."""
|
|
body = to_ui_ta1_ack(ta1_row)
|
|
assert body["id"] == ta1_row.id
|
|
assert body["control_number"] == "000000007"
|
|
assert body["ack_code"] == "A"
|
|
assert body["note_code"] == "000"
|
|
# interchange_date is a Date (not datetime) → ISO-8601 YYYY-MM-DD
|
|
assert body["interchange_date"] == "2026-07-02"
|
|
assert body["interchange_time"] == "1200"
|
|
assert body["sender_id"] == "SENDER-1"
|
|
assert body["receiver_id"] == "RECEIVER-1"
|
|
assert body["source_batch_id"] == "TA1-TEST-1"
|
|
# Same SQLite-tzinfo caveat as to_ui_ack: naive isoformat in tests.
|
|
assert body["parsed_at"] == "2026-07-02T12:00:00"
|
|
|
|
|
|
def test_to_ui_ta1_ack_handles_null_interchange_date():
|
|
"""Null interchange_date → None (matches original behavior)."""
|
|
with db.SessionLocal()() as s:
|
|
row = db.Ta1Ack(
|
|
source_batch_id="TA1-TEST-NULL",
|
|
control_number="000000099",
|
|
interchange_date=None,
|
|
interchange_time=None,
|
|
ack_code="R",
|
|
note_code="001",
|
|
ack_generated_date=None,
|
|
sender_id="S",
|
|
receiver_id="R",
|
|
parsed_at=datetime(2026, 7, 2, 12, 0, tzinfo=timezone.utc),
|
|
raw_json={"envelope": {}},
|
|
)
|
|
s.add(row)
|
|
s.commit()
|
|
s.refresh(row)
|
|
fresh = row
|
|
body = to_ui_ta1_ack(fresh)
|
|
assert body["interchange_date"] is None
|
|
assert body["interchange_time"] is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# to_ui_two77ca_ack (277CA)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_to_ui_two77ca_ack_round_trips_fields(two77ca_row):
|
|
"""to_ui_two77ca_ack must produce the exact ``_277ca_to_ui`` wire shape."""
|
|
body = to_ui_two77ca_ack(two77ca_row)
|
|
assert body["id"] == two77ca_row.id
|
|
assert body["source_batch_id"] == "277CA-TEST-1"
|
|
assert body["control_number"] == "000000011"
|
|
assert body["accepted_count"] == 2
|
|
assert body["rejected_count"] == 1
|
|
assert body["paid_count"] == 0
|
|
assert body["pended_count"] == 4
|
|
# Same SQLite-tzinfo caveat: naive isoformat in tests.
|
|
assert body["parsed_at"] == "2026-07-02T12:00:00"
|