"""Tests for the dedup-ed ack ID helpers (SP27 Task 1). Locks the contract for the helpers that ``scheduler.py`` + ``api.py`` will both import from one place in ``cyclone.handlers._ack_id``. """ from __future__ import annotations import hashlib from pathlib import Path from cyclone.handlers._ack_id import ( ack_count_summary, ack_synthetic_source_batch_id, two77ca_synthetic_source_batch_id, ) from cyclone.parsers.parse_999 import parse_999_text ACCEPTED = Path(__file__).parent / "fixtures" / "minimal_999.txt" REJECTED = Path(__file__).parent / "fixtures" / "minimal_999_rejected.txt" def _parse(path: Path): return parse_999_text(path.read_text(), input_file=path.name) def test_ack_count_summary_all_accepted(): result = _parse(ACCEPTED) recv, acc, rej, code = ack_count_summary(result) assert recv == 1 assert acc == 1 assert rej == 0 assert code == "A" def test_ack_count_summary_all_rejected(): result = _parse(REJECTED) recv, acc, rej, code = ack_count_summary(result) assert recv == 1 assert acc == 0 assert rej == 1 assert code == "R" def test_ack_count_summary_partial_uses_p_code(): # Synthesize a 2-set 999 inline with one AK5=A and one AK5=R. # ISA is exactly 106 chars (positions 0-105); the parser slices # positionally then splits the body on ``~``. # SE count = ST + AK1 + AK2_1 + AK5_1 + AK2_2 + AK5_2 + AK9 + SE = 8. partial_999 = ( "ISA*00* *00* *ZZ*SUBMITTERID *ZZ*RECEIVERID " "*240101*1200*^*00501*000000001*0*P*:~" "GS*FA*SUBMITTERID*RECEIVERID*20240101*1200*1*X*005010X231A1~" "ST*999*0001*005010X231A1~" "AK1*HC*0001*1*2~" "AK2*837*1~" "AK5*A~" "AK2*837*2~" "AK5*R~" "AK9*P*2*1*1~" "SE*8*0001~" "GE*1*1~" "IEA*1*000000001~" ) result = parse_999_text(partial_999, input_file="partial.999") recv, acc, rej, code = ack_count_summary(result) assert recv == 2 assert acc == 1 assert rej == 1 assert code == "P" def test_ack_synthetic_with_pcn_and_filename_is_deterministic_and_prefix(): bsid1 = ack_synthetic_source_batch_id( "000000001", pcn="PCN-12345", source_filename="tp_999.x12", ) bsid2 = ack_synthetic_source_batch_id( "000000001", pcn="PCN-12345", source_filename="tp_999.x12", ) assert bsid1 == bsid2, "same inputs must produce same id" assert bsid1.startswith("999-PCN-12345-") # Hash is 8 hex chars suffix = bsid1.split("-")[-1] assert len(suffix) == 8 and all( c in "0123456789abcdef" for c in suffix ), suffix def test_ack_synthetic_with_pcn_strips_whitespace(): bsid = ack_synthetic_source_batch_id( "000000001", pcn=" PCN ", source_filename="f.x12", ) assert bsid.startswith("999-PCN-") def test_ack_synthetic_without_pcn_uses_icn(): bsid = ack_synthetic_source_batch_id( "777777777", pcn=None, source_filename="f.x12", ) assert bsid.startswith("999-777777777-") # <= 32 chars total (VARCHAR(32) constraint) assert len(bsid) <= 32 def test_ack_synthetic_default_icn_when_empty(): bsid = ack_synthetic_source_batch_id("", pcn=None, source_filename=None) # No PCN, no filename → falls through to default ICN ``000000001`` # (no hash suffix without a filename). assert bsid == "999-000000001" def test_two77ca_synthetic_uses_icn(): assert two77ca_synthetic_source_batch_id("000012345") == "277CA-000012345" def test_two77ca_synthetic_empty_falls_back_to_default(): assert two77ca_synthetic_source_batch_id("") == "277CA-000000001" assert two77ca_synthetic_source_batch_id(None) == "277CA-000000001" def test_ack_count_summary_empty_set_responses_returns_zeros(): """A 999 envelope without any AK2/IK5 sets is malformed per the spec, but the helper must still return a sane (0, 0, 0, 'A') tuple — the scheduler adds it to result.errors and the caller decides.""" class _StubResult: set_responses = [] recv, acc, rej, code = ack_count_summary(_StubResult()) assert recv == 0 assert acc == 0 assert rej == 0 # rejected == 0 → "A" (no rejection signal in this degenerate case) assert code == "A" def test_ack_synthetic_uses_real_sha1_truncation(): """The hash should match hashlib.sha1(filename).hexdigest()[:8] so a future migration to a different hash is intentional, not drift.""" expected = hashlib.sha1(b"my-file.x12").hexdigest()[:8] bsid = ack_synthetic_source_batch_id( "000000001", pcn=None, source_filename="my-file.x12", ) assert bsid.endswith(f"-{expected}")