import json from datetime import date from decimal import Decimal from pathlib import Path import pytest from cyclone.parsers.models import ( Address, BatchSummary, BillingProvider, ClaimHeader, ClaimOutput, Envelope, Payer, Subscriber, ValidationReport, ) from cyclone.parsers.parse_837 import ParseResult from cyclone.parsers.writer import write_outputs def _claim(cid: str) -> ClaimOutput: return ClaimOutput( claim_id=cid, control_number="991102977", transaction_date=date(2026, 6, 11), billing_provider=BillingProvider(name="X", npi="1234567890", address=Address(line1="1", city="x", state="CO", zip="80000")), subscriber=Subscriber(first_name="J", last_name="D", member_id="M", dob=date(1980, 1, 1), gender="M", address=Address(line1="1", city="x", state="CO", zip="80000")), payer=Payer(name="P", id="SKCO0"), claim=ClaimHeader(claim_id=cid, total_charge=Decimal("100"), place_of_service="12", frequency_code="1", provider_signature="Y", assignment="Y", release_of_info="Y"), diagnoses=[], service_lines=[], validation=ValidationReport(passed=True, errors=[], warnings=[]), raw_segments=[], ) def _result(*cids: str) -> ParseResult: return ParseResult( envelope=Envelope(sender_id="X", receiver_id="Y", control_number="991102977", transaction_date=date(2026, 6, 11)), claims=[_claim(c) for c in cids], summary=BatchSummary(input_file="f", control_number="991102977", transaction_date=date(2026, 6, 11), total_claims=len(cids), passed=len(cids), failed=0), ) def test_write_outputs_creates_one_file_per_claim(tmp_path: Path): result = _result("C1", "C2") paths = write_outputs(result, tmp_path) assert len(paths) == 2 for p in paths: assert p.exists() assert p.suffix == ".json" data = json.loads(p.read_text()) assert "validation" in data def test_filename_pattern(tmp_path: Path): result = _result("C1") paths = write_outputs(result, tmp_path) assert paths[0].name == "claim-991102977-20260611-C1.json" def test_filename_sanitizes_unsafe_chars(tmp_path: Path): bad = _claim("C/1:*?") result = ParseResult( envelope=result.envelope if False else None, # type: ignore claims=[bad], summary=BatchSummary(input_file="f", total_claims=1, passed=1, failed=0), ) # use explicit envelope # Simpler: rebuild from cyclone.parsers.models import Envelope as E result = ParseResult( envelope=E(sender_id="X", receiver_id="Y", control_number="991102977", transaction_date=date(2026, 6, 11)), claims=[bad], summary=BatchSummary(input_file="f", total_claims=1, passed=1, failed=0), ) paths = write_outputs(result, tmp_path) # All non-alnum chars except - and _ replaced with - assert all(c.isalnum() or c in "-_" for c in paths[0].stem) def test_summary_json_written(tmp_path: Path): result = _result("C1") write_outputs(result, tmp_path) summary_path = tmp_path / "summary.json" assert summary_path.exists() data = json.loads(summary_path.read_text()) assert data["total_claims"] == 1 assert data["passed"] == 1