"""Loosen parse_inbound_filename to accept filenames without the _file_type.x12 suffix (e.g. the 6/15-6/19 Gainwell 835 batch). SP27 Task 7. Background: Gainwell's production filer has shipped at least two inbound filename forms — the spec form ``tp{tpid}-{orig_tx}_M{track}-{ts}-1of1_{ft}.x12`` and a shorter ``tp{tpid}-{orig_tx}_M{track}-{ts}-1of1.x12`` (no ``_{ft}``). The 6/15 through 6/19 835 batch arrived in the shorter form, and the scheduler silently dropped them because the strict ``INBOUND_RE`` rejected the filenames outright — a silent-failure mode that took ~5 days of production data to spot. The fix: add a second regex ``INBOUND_RE_LOOSE`` that omits the ``_{ft}`` segment, and fall back to it when the strict form fails. In the loose form, the token between ``-`` and ``_M`` doubles as both ``orig_tx`` and ``file_type`` (since there's no separate ``_{file_type}`` suffix to disambiguate). """ from __future__ import annotations import pytest from cyclone.edi.filenames import parse_inbound_filename def test_filename_with_explicit_835_suffix(): """Existing happy-path filename is unchanged (spec form still wins).""" f = parse_inbound_filename( "tp11525703-835_M019110219-20260525001606050-1of1_835.x12" ) assert f.file_type == "835" assert f.orig_tx == "835" assert f.ext == "x12" def test_filename_without_suffix_with_orig_tx_835(): """New: 6/15-6/19 Gainwell pattern — no _file_type.x12, orig_tx=835.""" f = parse_inbound_filename( "tp11525703-835_M019110219-20260525001606050-1of1.x12" ) assert f.file_type == "835" # orig_tx falls back to the disambiguator token (between "-" and "_M") # when the suffix is missing. This preserves the historical shape # the downstream code (parse_inbound_filename callers) expects. assert f.orig_tx == "835" assert f.ext == "x12" assert f.tpid == "11525703" def test_filename_without_suffix_with_orig_tx_999_falls_back_to_999(): """orig_tx is the disambiguator when the suffix is missing.""" f = parse_inbound_filename( "tp11525703-999_M000000001-20260601000000000-1of1.x12" ) assert f.file_type == "999" assert f.orig_tx == "999" def test_filename_without_suffix_277ca(): f = parse_inbound_filename( "tp11525703-277CA_M000000001-20260601000000000-1of1.x12" ) assert f.file_type == "277CA" assert f.orig_tx == "277CA" def test_filename_unknown_orig_tx_rejected(): """orig_tx=ENCR is in ALLOWED_FILE_TYPES — should still accept. ENCR is the encrypted-payload wrapper Gainwell occasionally sends for non-EDI payloads. The filename legitimately lacks both the ``_{ft}`` suffix and the upstream transaction marker. """ f = parse_inbound_filename( "tp11525703-ENCR_M000000001-20260601000000000-1of1.x12" ) assert f.file_type == "ENCR" assert f.orig_tx == "ENCR" def test_filename_invalid_extension_still_rejected(): """Inbound .txt files are not X12 — rejected even with valid orig_tx. Pins the behavior that the loose-form fallback does NOT relax the ``.x12`` extension constraint. If a future refactor loosens the ext check, this test will catch it. """ with pytest.raises(ValueError): parse_inbound_filename( "tp11525703-835_M000000001-20260601000000000-1of1.txt" ) def test_filename_loose_form_unknown_type_rejected(): """The loose form must still enforce ALLOWED_FILE_TYPES. A 4-char token like ``ABCD`` matches the loose regex's ``{3,5}`` shape, so without this check it would slip through. The parser rejects it the same way the strict form rejects ``_ABCD.x12``. """ with pytest.raises(ValueError, match="not in allowed HCPF set"): parse_inbound_filename( "tp11525703-ABCD_M000000001-20260601000000000-1of1.x12" ) def test_filename_loose_form_5char_cap_rejected(): """The ``{3,5}`` cap prevents the regex from swallowing the ``_M`` token. A 6-char token would over-eat the next segment, so the loose regex refuses it. ``999XX6`` is 6 chars, well over the 5-char cap. """ with pytest.raises(ValueError): parse_inbound_filename( "tp11525703-999XX6_M000000001-20260601000000000-1of1.x12" ) def test_filename_strict_form_takes_precedence_over_loose(): """The strict form is tried first; the loose form never shadows it. ``tp1-835_M…-1of1_835.x12`` matches the strict regex (orig_tx=835, file_type=835). Pinning this guarantees a refactor that swaps the order can't silently change the parsed shape. """ from cyclone.edi.filenames import INBOUND_RE, INBOUND_RE_LOOSE name = "tp1-835_M000000001-20260601000000000-1of1_835.x12" # Sanity check: both regexes would match this string on their own # (the loose regex stops at the underscore before _835, the strict # regex extends through the suffix). The point is the parser # returns the strict shape — file_type from the suffix group, not # from the disambiguator token. assert INBOUND_RE.match(name) is not None f = parse_inbound_filename(name) assert f.file_type == "835" assert f.orig_tx == "835" # not "835" by accident — both happen to be 835 def test_is_inbound_filename_accepts_loose_form(): """`is_inbound_filename` must also accept the loose form. The strict ``is_inbound_filename`` previously returned False for suffix-less filenames, which is the same silent-drop bug the loose-form ``parse_inbound_filename`` fix addresses. The two must never disagree. """ from cyclone.edi.filenames import is_inbound_filename # Suffix-less form assert is_inbound_filename( "tp11525703-835_M019110219-20260525001606050-1of1.x12" ) is True # Spec form still works assert is_inbound_filename( "tp11525703-837P_M019048402-20260520231513488-1of1_999.x12" ) is True # Garbage still rejected assert is_inbound_filename("completely-garbage.x12") is False assert is_inbound_filename("") is False