"""SP9 — HCPF X12 File Naming Standards helper tests.""" from __future__ import annotations from datetime import datetime from zoneinfo import ZoneInfo import pytest from cyclone.edi.filenames import ( ALLOWED_FILE_TYPES, OUTBOUND_RE, INBOUND_RE, build_outbound_filename, is_inbound_filename, is_outbound_filename, parse_inbound_filename, ) from cyclone.providers import InboundFilename MT = ZoneInfo("America/Denver") # ----- build_outbound_filename -------------------------------------------- def test_build_outbound_with_explicit_mt(): now = datetime(2026, 6, 20, 13, 22, 43, 505_000, tzinfo=MT) name = build_outbound_filename("11525703", "837P", now_mt=now) assert name == "11525703-837P-20260620132243505-1of1.x12" def test_build_outbound_default_extension(): now = datetime(2026, 6, 20, 13, 22, 43, 505_000, tzinfo=MT) name = build_outbound_filename("11525703", "837P", now_mt=now) assert name.endswith(".x12") def test_build_outbound_custom_extension(): now = datetime(2026, 6, 20, 13, 22, 43, 505_000, tzinfo=MT) name = build_outbound_filename("11525703", "837P", ext="txt", now_mt=now) assert name == "11525703-837P-20260620132243505-1of1.txt" def test_build_outbound_uses_mt_when_no_arg(): # Snapshot test — the timestamp will be very recent; check format only name = build_outbound_filename("11525703", "837P") assert OUTBOUND_RE.match(name), name parts = name.split("-") assert len(parts) == 4 assert len(parts[2]) == 17 # yyyymmddhhmmssSSS def test_build_outbound_rejects_non_numeric_tpid(): now = datetime(2026, 6, 20, tzinfo=MT) with pytest.raises(ValueError, match="tpid must be digits"): build_outbound_filename("abc123", "837P", now_mt=now) def test_build_outbound_rejects_invalid_tx(): now = datetime(2026, 6, 20, tzinfo=MT) with pytest.raises(ValueError, match="tx must be uppercase alnum"): build_outbound_filename("11525703", "837-lower", now_mt=now) def test_build_outbound_rejects_naive_dt(): with pytest.raises(ValueError, match="timezone-aware"): build_outbound_filename("11525703", "837P", now_mt=datetime(2026, 6, 20)) def test_build_outbound_converts_other_tz_to_mt(): # 2026-06-20 19:22:43 UTC = 2026-06-20 13:22:43 MT (during DST) from datetime import timezone now_utc = datetime(2026, 6, 20, 19, 22, 43, 505_000, tzinfo=timezone.utc) name = build_outbound_filename("11525703", "837P", now_mt=now_utc) assert "20260620132243505" in name # ----- parse_inbound_filename --------------------------------------------- def test_parse_inbound_999_real_prodfile(): # Real production 999 filename from docs/prodfiles/FromHPE/ name = "TP11525703-837P_M019048402-20260520231513488-1of1_999.x12" parsed = parse_inbound_filename(name) assert parsed.tpid == "11525703" assert parsed.orig_tx == "837P" assert parsed.tracking == "M019048402" assert parsed.ts == "20260520231513488" assert parsed.file_type == "999" assert parsed.ext == "x12" def test_parse_inbound_ta1_real_prodfile(): name = "TP11525703-837P_M019044969-20260520180505477-1of1_TA1.x12" parsed = parse_inbound_filename(name) assert parsed.file_type == "TA1" assert parsed.tracking == "M019044969" def test_parse_inbound_277(): name = "TP11525703-837P_M019110219-20260601003507042-1of1_277.x12" parsed = parse_inbound_filename(name) assert parsed.file_type == "277" def test_parse_inbound_rejects_missing_tp_prefix(): with pytest.raises(ValueError, match="Not a valid HCPF inbound"): parse_inbound_filename("11525703-837P_M019048402-20260520231513488-1of1_999.x12") def test_parse_inbound_rejects_wrong_segment_count(): with pytest.raises(ValueError): parse_inbound_filename("TP11525703_837P_M019048402-1of1_999.x12") def test_parse_inbound_rejects_unknown_file_type(): with pytest.raises(ValueError, match="not in allowed HCPF set"): parse_inbound_filename("TP11525703-837P_M019048402-20260520231513488-1of1_XXX.x12") def test_parse_inbound_rejects_non_x12_ext(): with pytest.raises(ValueError): parse_inbound_filename("TP11525703-837P_M019048402-20260520231513488-1of1_999.txt") # ----- round-trip --------------------------------------------------------- def test_roundtrip_outbound_to_inbound(): # Outbound tpid is bare (no TP); inbound tpid is bare inside TP{...} # The two regexes use different shapes — round-trip via tpid only. now = datetime(2026, 6, 20, 13, 22, 43, 505_000, tzinfo=MT) out = build_outbound_filename("11525703", "837P", now_mt=now) assert OUTBOUND_RE.match(out) assert "11525703" in out assert "837P" in out assert out.endswith("1of1.x12") # ----- validators --------------------------------------------------------- def test_is_outbound_filename(): assert is_outbound_filename("11525703-837P-20260620132243505-1of1.x12") assert not is_outbound_filename("TP11525703-837P-20260620132243505-1of1.x12") assert not is_outbound_filename("not-a-filename") def test_is_inbound_filename(): assert is_inbound_filename("TP11525703-837P_M019048402-20260520231513488-1of1_999.x12") assert not is_inbound_filename("11525703-837P-20260620132243505-1of1.x12") def test_allowed_file_types_includes_277ca(): assert "277CA" in ALLOWED_FILE_TYPES assert "TA1" in ALLOWED_FILE_TYPES assert "999" in ALLOWED_FILE_TYPES assert "835" in ALLOWED_FILE_TYPES