"""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"