133 lines
5.3 KiB
Python
133 lines
5.3 KiB
Python
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"] == []
|