from __future__ import annotations import sqlite3 from jarca_ledger_mcp import service def test_exact_run_replay_after_correction_reconstructs_original_persisted_run(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") service.create_manual_asset("현금", 100, "2026-06-30", "cash", db_path=db_path) service.create_manual_asset("평가 대기", None, "2026-06-30", "other", db_path=db_path) first = service.close_asset_snapshot( "2026-06-30", source_note="synthetic initial close", memo="synthetic initial memo", idempotency_key="snapshot-close-a", allow_missing_exclusions=True, approved_by="reviewer-a", approval_reason="synthetic initial approval", approval_reference="approval-a", db_path=db_path, ) assert first["success"] is True assert first["close_status"] == "conditional" assert first["is_correction"] is False assert first["warnings"] assert first["approval"]["approval_reference"] == "approval-a" assert first["provenance"]["snapshot_run_id"] == first["snapshot_run_id"] corrected = service.close_asset_snapshot( "2026-06-30", source_note="synthetic correction close", memo="synthetic correction memo", correction_reason="synthetic correction", idempotency_key="snapshot-close-b", allow_missing_exclusions=True, approved_by="reviewer-b", approval_reason="synthetic correction approval", approval_reference="approval-b", db_path=db_path, ) assert corrected["success"] is True assert corrected["is_correction"] is True assert corrected["snapshot_run_id"] != first["snapshot_run_id"] assert corrected["correction"]["correction_of_run_id"] == first["snapshot_run_id"] assert corrected["approval"]["approval_reference"] == "approval-b" assert corrected["snapshot_run_id"] not in first["inserted_snapshot_ids"] with sqlite3.connect(db_path) as conn: before_replay = conn.execute("SELECT COUNT(*) FROM asset_snapshots").fetchone()[0] replay = service.close_asset_snapshot( "2026-06-30", source_note="synthetic initial close", memo="synthetic initial memo", idempotency_key="snapshot-close-a", allow_missing_exclusions=True, approved_by="reviewer-a", approval_reason="synthetic initial approval", approval_reference="approval-a", db_path=db_path, ) with sqlite3.connect(db_path) as conn: after_replay = conn.execute("SELECT COUNT(*) FROM asset_snapshots").fetchone()[0] assert replay == first assert replay["snapshot_run_id"] == first["snapshot_run_id"] assert replay["inserted_snapshot_ids"] == first["inserted_snapshot_ids"] assert replay["correction"] == first["correction"] assert replay["approval"] == first["approval"] assert replay["warnings"] == first["warnings"] assert replay["provenance"] == first["provenance"] assert replay["included_items"] == first["included_items"] assert replay["excluded_items"] == first["excluded_items"] assert replay["total_assets"] == first["total_assets"] assert replay["total_liabilities"] == first["total_liabilities"] assert replay["net_worth"] == first["net_worth"] assert replay["snapshot_run_id"] != corrected["snapshot_run_id"] assert corrected["snapshot_run_id"] not in replay["inserted_snapshot_ids"] assert after_replay == before_replay def test_empty_run_replay_after_nonempty_correction_preserves_original_empty_payload(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") service.create_ledger_account("누락 계좌", db_path=db_path) first = service.close_asset_snapshot( "2026-06-30", idempotency_key="empty-close-a", allow_missing_exclusions=True, approved_by="reviewer-a", approval_reason="approve empty", approval_reference="approval-a", db_path=db_path, ) assert first["success"] is True assert first["inserted_count"] == 0 assert first["included_items"] == [] service.create_manual_asset("후속 자산", 100, "2026-06-30", "cash", db_path=db_path) corrected = service.close_asset_snapshot( "2026-06-30", correction_reason="add verified asset", idempotency_key="nonempty-close-b", allow_missing_exclusions=True, approved_by="reviewer-b", approval_reason="approve correction", approval_reference="approval-b", db_path=db_path, ) assert corrected["success"] is True assert corrected["inserted_count"] == 1 # Restore the original live authority while retaining the later immutable # correction run, so replay reaches the original-key reconstruction path. with sqlite3.connect(db_path) as conn: conn.execute("DELETE FROM manual_assets WHERE name=?", ("후속 자산",)) replay = service.close_asset_snapshot( "2026-06-30", idempotency_key="empty-close-a", allow_missing_exclusions=True, approved_by="reviewer-a", approval_reason="approve empty", approval_reference="approval-a", db_path=db_path, ) assert replay == first assert replay["inserted_count"] == 0 assert replay["inserted_snapshot_ids"] == [] assert replay["included_items"] == []