diff --git a/ingestion/parsers.py b/ingestion/parsers.py index 4b8a011..160e7ba 100644 --- a/ingestion/parsers.py +++ b/ingestion/parsers.py @@ -155,8 +155,11 @@ def partial_context_from_creation_line(data: bytes) -> dict[str, str]: creation = _creation_date(lines[20]) start_year = creation.year if creation.month == 12 else creation.year - 1 start_month = creation.month % 12 + 1 - start = date(start_year, start_month, 1) - end = date(creation.year, creation.month, monthrange(creation.year, creation.month)[1]) + try: + start = date(start_year, start_month, 1) + end = date(creation.year, creation.month, monthrange(creation.year, creation.month)[1]) + except ValueError: + raise ParseError('INVALID_CREATION_DATE') from None if (end - start).days > 366: raise ParseError('INVALID_CREATION_DATE') return {'start_date': start.isoformat(), 'end_date': end.isoformat()} diff --git a/tests/test_guided_import_workflow.py b/tests/test_guided_import_workflow.py index 1dd960c..63c49ea 100644 --- a/tests/test_guided_import_workflow.py +++ b/tests/test_guided_import_workflow.py @@ -254,6 +254,23 @@ def test_partial_missing_context_has_safe_code_and_no_preview(source): assert not ImportBatch.objects.exists() +def test_partial_context_year_underflow_does_not_interrupt_another_ready_file(source, bank): + card = create_account(name="Synthetic boundary card", kind="CREDIT_CARD", actor=ACTOR) + data = ("\n" * 20 + "Data de emissão: 20/01/0001\n" + + f"05/01{'Synthetic boundary row':43}BR 12,50 0,00\n").encode() + exceptional = source(data, "partial.txt") + ready = source() + results = [prepare(discovered, account) for discovered, account in ((exceptional, card), (ready, bank))] + assert results[0].state == "NEEDS_ATTENTION" + assert results[0].code == "INVALID_CREATION_DATE" + assert results[0].batch_id is None + assert results[1].state == "READY" + assert commit(results[1]).counts["created"] == 2 + assert Transaction.objects.count() == SourceRecord.objects.count() == 2 + assert not ImportBatch.objects.filter(profile="OUROCARD_PARTIAL_V1").exists() + assert_no_open_preview() + + def test_partial_valid_context_resolves_new_year_and_commits(source): card = create_account(name="Synthetic partial card", kind="CREDIT_CARD", actor=ACTOR) data = ("\n" * 20 + "Data de emissão: 20/01/2026\n" diff --git a/tests/test_import_parsers.py b/tests/test_import_parsers.py index c873030..32d33c1 100644 --- a/tests/test_import_parsers.py +++ b/tests/test_import_parsers.py @@ -154,6 +154,20 @@ def test_partial_context_resolves_december_rows_to_the_previous_year_after_janua assert rows[0].posting_date == '2025-12-31' +@pytest.mark.parametrize('creation', ['20/01/0001', '15/11/0001']) +def test_partial_context_rejects_derived_interval_year_underflow(creation): + data = ('\n' * 20 + f'Data de emissão: {creation}\n').encode() + with pytest.raises(ParseError, match='^INVALID_CREATION_DATE$'): + partial_context_from_creation_line(data) + + +def test_partial_context_accepts_year_one_when_derived_bounds_are_representable(): + data = ('\n' * 20 + 'Data de emissão: 15/12/0001\n').encode() + assert partial_context_from_creation_line(data) == { + 'start_date': '0001-01-01', 'end_date': '0001-12-31', + } + + @pytest.mark.parametrize('filename,profile,amounts', [ ('bank.csv', 'BANK_CSV_V1', ['-12.34','100.00']), ('full.txt', 'OUROCARD_FULL_V1', ['-12.34','8.11','33.00']),