"""Synthetic direct-ORM checks for the PostgreSQL persistence boundary.""" from datetime import date from decimal import Decimal import pytest from django.db import IntegrityError from django.db.models.deletion import ProtectedError from finance.models import ( Account, AuditEvent, CardStatement, Category, InstallmentPlan, Payee, SourceRecord, Transaction, Transfer, ) pytestmark = pytest.mark.django_db(transaction=True) @pytest.fixture def account(): return Account.objects.create(name="Synthetic bank", kind="BANK") def post(account, **changes): values = dict(account=account, amount=Decimal("-10.00"), posting_date=date(2026, 9, 1), kind="EXPENSE") values.update(changes) return Transaction.objects.create(**values) @pytest.mark.parametrize("kind,amount", [ ("EXPENSE", "0"), ("EXPENSE", "1"), ("INCOME", "-1"), ("REFUND", "-1"), ("TRANSFER", "0"), ("OPENING", "0"), ("INVALID", "-1"), ]) def test_database_rejects_invalid_transaction_signs(account, kind, amount): with pytest.raises(IntegrityError): post(account, kind=kind, amount=Decimal(amount)) @pytest.mark.parametrize("currency", ["brl", "B1L", "BR", "ÁBC"]) def test_database_rejects_invalid_currency(currency): with pytest.raises(IntegrityError): Account.objects.create(name="Synthetic account", kind="BANK", currency=currency) def test_database_rejects_invalid_reference_choices(): with pytest.raises(IntegrityError): Account.objects.create(name="Synthetic account", kind="INVALID") with pytest.raises(IntegrityError): Category.objects.create(name="Synthetic category", kind="INVALID") def test_opening_is_unique_and_cannot_have_associations(account): post(account, kind="OPENING") with pytest.raises(IntegrityError): post(account, kind="OPENING") payee = Payee.objects.create(name="Synthetic shop") other = Account.objects.create(name="Other synthetic bank", kind="BANK") with pytest.raises(IntegrityError): post(other, kind="OPENING", payee=payee) def test_refund_reference_only_allowed_on_refund(account): expense = post(account) with pytest.raises(IntegrityError): post(account, refund_of=expense) with pytest.raises(IntegrityError): Transaction.objects.filter(pk=expense.pk).update( kind="REFUND", amount=Decimal("10"), refund_of=expense) def test_transfer_cannot_have_category(account): category = Category.objects.create(name="Synthetic expense", kind="EXPENSE") with pytest.raises(IntegrityError): post(account, kind="TRANSFER", category=category) def test_installment_pair_number_kind_and_uniqueness(account): plan = InstallmentPlan.objects.create(account=account, total_installments=10) for changes in [dict(installment_plan=plan), dict(installment_number=3), dict(installment_plan=plan, installment_number=0), dict(installment_plan=plan, installment_number=3, kind="REFUND", amount=Decimal("10"))]: with pytest.raises(IntegrityError): post(account, **changes) post(account, installment_plan=plan, installment_number=3) with pytest.raises(IntegrityError): post(account, installment_plan=plan, installment_number=3) with pytest.raises(IntegrityError): InstallmentPlan.objects.create(account=account, total_installments=0) def test_statement_date_order_and_identity(account): values = dict(account=account, period_start=date(2026, 8, 1), period_end=date(2026, 8, 31), due_date=date(2026, 9, 10)) with pytest.raises(IntegrityError): CardStatement.objects.create(**{**values, "period_end": date(2026, 7, 31)}) with pytest.raises(IntegrityError): CardStatement.objects.create(**{**values, "due_date": date(2026, 8, 30)}) CardStatement.objects.create(**values) with pytest.raises(IntegrityError): CardStatement.objects.create(**values) def test_source_identity_format_and_protected_history(account): activity = post(account) values = dict(transaction=activity, document_hash="a" * 64, source_format="BANK_CSV", parser_version="synthetic-v1", item_locator="row:1") for changes in [dict(document_hash="bad"), dict(source_format="OFX")]: with pytest.raises(IntegrityError): SourceRecord.objects.create(**{**values, **changes}) SourceRecord.objects.create(**values) with pytest.raises(IntegrityError): SourceRecord.objects.create(**values) with pytest.raises(ProtectedError): activity.delete() with pytest.raises(ProtectedError): account.delete() def test_transfer_distinct_and_unique_roles(account): outgoing = post(account, kind="TRANSFER") incoming = post(account, kind="TRANSFER", amount=Decimal("10")) with pytest.raises(IntegrityError): Transfer.objects.create(outgoing=outgoing, incoming=outgoing) Transfer.objects.create(outgoing=outgoing, incoming=incoming) with pytest.raises(IntegrityError): Transfer.objects.create(outgoing=outgoing, incoming=post(account, amount=Decimal("10"), kind="TRANSFER")) with pytest.raises(IntegrityError): Transfer.objects.create(outgoing=post(account, kind="TRANSFER"), incoming=incoming) def test_audit_actor_and_outcome_choices(): values = dict(actor_kind="HUMAN", actor_identifier="synthetic-owner", operation="create_account", outcome="SUCCESS") for changes in [dict(actor_kind="INVALID"), dict(outcome="INVALID")]: with pytest.raises(IntegrityError): AuditEvent.objects.create(**{**values, **changes})