"""Synthetic acceptance workflow for the complete local MVP.""" import json from contextlib import contextmanager from decimal import Decimal from pathlib import Path from types import SimpleNamespace from zipfile import ZipFile import pytest from django.test import Client from django.urls import reverse from core.backup import ( DatabaseSnapshot, DatabaseTarget, create_backup, verify_backup, ) from finance.exports import build_export from finance.models import SourceRecord, Transaction from finance.services import Actor, create_account, create_category from ingestion import services as import_services pytestmark = pytest.mark.django_db(transaction=True) ACTOR = Actor(kind="AGENT", identifier="synthetic-mvp-acceptance") FIXTURE = Path("tests/fixtures/synthetic/ingestion/bank.csv") def test_synthetic_import_to_report_export_and_backup_workflow( tmp_path, settings, monkeypatch ): account = create_account( name="Synthetic acceptance bank", kind="BANK", currency="BRL", actor=ACTOR, ) category = create_category( name="Synthetic acceptance spending", kind="EXPENSE", actor=ACTOR, ) batch = import_services.create_preview( data=FIXTURE.read_bytes(), profile="BANK_CSV_V1", section_mapping={"1": str(account.pk)}, actor=ACTOR, ) assert Transaction.objects.count() == 0 decisions = [ {"locator": item.locator, "action": "CREATE"} for item in batch.items.order_by("locator") ] batch = import_services.apply_decisions( batch_id=batch.pk, revision=batch.revision, decisions=decisions, actor=ACTOR, ) batch = import_services.commit_preview( batch_id=batch.pk, revision=batch.revision, actor=ACTOR, ) assert batch.result["created"] == 2 assert Transaction.objects.count() == SourceRecord.objects.count() == 2 expense = Transaction.objects.get(kind=Transaction.Kind.EXPENSE) browser = Client(enforce_csrf_checks=True) form = browser.get(reverse("transaction-annotate", args=[expense.pk])) csrf_token = form.cookies["csrftoken"].value response = browser.post( reverse("transaction-annotate", args=[expense.pk]), { "csrfmiddlewaretoken": csrf_token, "description": expense.description, "payee": "", "category": str(category.pk), }, ) assert response.status_code == 302 expense.refresh_from_db() assert expense.category_id == category.pk report = browser.get( reverse("monthly-report"), {"month": "2026-02", "currency": "BRL"}, ) assert report.status_code == 200 assert report.context["summary"].inflow == Decimal("100.00") assert report.context["summary"].expense == Decimal("12.34") assert report.context["summary"].net_activity == Decimal("87.66") assert report.context["spending_rows"][0].category_name == category.name export = build_export() export_path = tmp_path / export.filename export_path.write_bytes(export.content) with ZipFile(export_path) as archive: manifest = json.loads(archive.read("manifest.json")) assert manifest["format"] == "gizmoduck-export" assert manifest["files"]["transactions.csv"]["rows"] == 2 assert manifest["files"]["source_records.csv"]["rows"] == 2 assert category.name.encode() in archive.read("categories.csv") settings.BACKUP_ROOT = tmp_path settings.BACKUP_VERIFY_ALLOWED = True snapshot = DatabaseSnapshot( migrations=(("finance", "0001_initial"), ("ingestion", "0001_initial")), row_counts={"finance_transaction": 2, "finance_sourcerecord": 2}, ) @contextmanager def exported_snapshot(_target): yield "synthetic-mvp-snapshot", snapshot class Runner: def __init__(self): self.calls = [] def __call__(self, argv, **kwargs): self.calls.append(list(argv)) if argv[0] == "pg_dump" and argv != ["pg_dump", "--version"]: dump_name = next( value.removeprefix("--file=") for value in argv if value.startswith("--file=") ) Path(dump_name).write_bytes(b"PGDMP synthetic acceptance") return SimpleNamespace(stdout=b"pg_dump (PostgreSQL) 17.6\n") monkeypatch.setattr("core.backup._exported_snapshot", exported_snapshot) backup_runner = Runner() backup = create_backup("synthetic-mvp.dump", runner=backup_runner) assert any(call[0] == "pg_dump" for call in backup_runner.calls) assert json.loads(backup.manifest_path.read_text(encoding="utf-8"))[ "row_counts" ] == snapshot.row_counts target = DatabaseTarget( host="restore-db", port="5432", name="gizmoduck_restore", user="gizmoduck_restore", password="synthetic-restore-password", ) observed = iter( [ DatabaseSnapshot(migrations=(), row_counts={}), snapshot, ] ) monkeypatch.setattr("core.backup._read_snapshot", lambda _target: next(observed)) restore_runner = Runner() result = verify_backup(backup.name, target, runner=restore_runner) assert result.row_counts == snapshot.row_counts assert restore_runner.calls[0][0] == "pg_restore" assert "--single-transaction" in restore_runner.calls[0]