from __future__ import annotations import sqlite3 from jarca_ledger_mcp import service def _approval_kwargs(): return { "allow_missing_exclusions": True, "approved_by": "reviewer", "approval_reason": "reviewed", "approval_reference": "review-63", } def test_f001_preview_fingerprint_uses_same_unredacted_authority_and_rejects_drift(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") service.create_manual_asset("현금", 100, "2026-06-30", "cash", db_path=db_path) preview = service.preview_asset_snapshot_close("2026-06-30", db_path=db_path) closed = service.close_asset_snapshot( "2026-06-30", preview_fingerprint=preview["preview_fingerprint"], db_path=db_path ) assert closed["success"] is True service.update_manual_asset( next(iter(service.list_manual_assets(db_path=db_path)["manual_assets"]))["id"], valuation_amount=101, db_path=db_path, ) with sqlite3.connect(db_path) as conn: rows_before = conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] drifted = service.close_asset_snapshot( "2026-06-30", preview_fingerprint=preview["preview_fingerprint"], db_path=db_path ) with sqlite3.connect(db_path) as conn: rows_after = conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] assert drifted["success"] is False assert drifted["error_code"] == "authority_drift" assert rows_after == rows_before def test_f002_close_replay_is_byte_for_byte_equal_for_mixed_account_manual_as_of(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") account = service.create_ledger_account( "투자계좌", type="investment", currency="KRW", db_path=db_path ) service.register_account_valuation( account["account"]["id"], "2026-06-29", 200, valuation_kind="total_value", verification_status="verified", idempotency_key="v-29", db_path=db_path, ) service.create_manual_asset("보증금", 300, "2026-06-30", "real_estate", db_path=db_path) first = service.close_asset_snapshot( "2026-06-30", selection_policy="as_of", idempotency_key="mixed-replay", allow_as_of_carry_forward=True, **_approval_kwargs(), db_path=db_path, ) assert first["success"] is True replay = service.close_asset_snapshot( "2026-06-30", selection_policy="as_of", idempotency_key="mixed-replay", allow_as_of_carry_forward=True, **_approval_kwargs(), db_path=db_path, ) assert replay == first def test_f002_correction_replay_preserves_full_initial_response(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") service.create_manual_asset("현금", 100, "2026-06-30", "cash", db_path=db_path) first = service.close_asset_snapshot( "2026-06-30", idempotency_key="correction-initial", db_path=db_path ) assert first["success"] is True service.update_manual_asset( next(iter(service.list_manual_assets(db_path=db_path)["manual_assets"]))["id"], valuation_amount=120, db_path=db_path, ) corrected = service.close_asset_snapshot( "2026-06-30", correction_reason="updated valuation", idempotency_key="correction-run", db_path=db_path, ) assert corrected["success"] is True assert any(warning["code"] == "existing_snapshot_rows" for warning in corrected["warnings"]) correction_replay = service.close_asset_snapshot( "2026-06-30", correction_reason="updated valuation", idempotency_key="correction-run", db_path=db_path, ) assert correction_replay == corrected initial_replay = service.close_asset_snapshot( "2026-06-30", idempotency_key="correction-initial", db_path=db_path ) assert initial_replay == first def test_f006_initial_replay_survives_multiple_later_corrections(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") service.create_manual_asset( "현금 A", 100, "2026-06-30", "cash", db_path=db_path ) service.create_manual_asset( "현금 B", 200, "2026-06-30", "cash", db_path=db_path ) assets = { item["name"]: item["id"] for item in service.list_manual_assets(db_path=db_path)["manual_assets"] } first = service.close_asset_snapshot( "2026-06-30", idempotency_key="multi-initial", db_path=db_path ) assert first["success"] is True service.update_manual_asset(assets["현금 A"], valuation_amount=110, db_path=db_path) first_correction = service.close_asset_snapshot( "2026-06-30", correction_reason="first correction", idempotency_key="multi-correction-1", db_path=db_path, ) assert first_correction["success"] is True service.update_manual_asset(assets["현금 A"], valuation_amount=120, db_path=db_path) second_correction = service.close_asset_snapshot( "2026-06-30", correction_reason="second correction", idempotency_key="multi-correction-2", db_path=db_path, ) assert second_correction["success"] is True replay = service.close_asset_snapshot( "2026-06-30", idempotency_key="multi-initial", db_path=db_path ) assert replay == first service.update_manual_asset(assets["현금 B"], valuation_amount=220, db_path=db_path) drifted_replay = service.close_asset_snapshot( "2026-06-30", idempotency_key="multi-initial", db_path=db_path ) assert drifted_replay["success"] is False assert drifted_replay["error_code"] == "idempotency_conflict" def test_f003_zero_row_conditional_runs_are_timeseries_authority_with_aliases(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="zero-initial", **_approval_kwargs(), db_path=db_path ) assert first["success"] is True assert first["inserted_count"] == 0 assert first["total_assets"] == first["total_liabilities"] == first["net_worth"] == 0 corrected = service.close_asset_snapshot( "2026-06-30", correction_reason="reconfirm zero", idempotency_key="zero-correction", **_approval_kwargs(), db_path=db_path, ) assert corrected["success"] is True assert corrected["inserted_count"] == 0 series = service.get_net_worth_timeseries( start_date="2026-06-01", end_date="2026-06-30", db_path=db_path ) assert series["success"] is True assert len(series["points"]) == 1 point = series["points"][0] assert point["basis_date"] == "2026-06-30" assert point["net_worth"] == point["total_assets"] == point["total_liabilities"] == 0 assert point["close_status"] == "conditional" assert point["approval"]["approval_reference"] == "review-63" assert point["excluded_items"] assert point["snapshot_run_id"] == corrected["snapshot_run_id"] assert point["provenance"]["monthly_source_snapshot_id"] == corrected["snapshot_run_id"] def test_f004_closed_report_restores_frozen_valuation_identity_without_legacy_invention(tmp_path): db_path = str(tmp_path / "ledger.sqlite3") account = service.create_ledger_account("계좌", db_path=db_path) valuation = service.register_account_valuation( account["account"]["id"], "2026-06-29", 500, verification_status="verified", idempotency_key="frozen-v1", db_path=db_path, ) first = service.close_asset_snapshot( "2026-06-30", selection_policy="as_of", idempotency_key="frozen-close", allow_as_of_carry_forward=True, **_approval_kwargs(), db_path=db_path, ) assert first["success"] is True report_before = service.get_asset_snapshot_report("2026-06-30", db_path=db_path) item_before = report_before["included_items"][0] assert item_before["valuation_id"] == valuation["valuation"]["id"] assert item_before["currency"] == "KRW" service.register_account_valuation( account["account"]["id"], "2026-06-29", 900, verification_status="verified", idempotency_key="frozen-v2", db_path=db_path, ) report_after = service.get_asset_snapshot_report("2026-06-30", db_path=db_path) assert report_after["included_items"][0]["valuation_id"] == item_before["valuation_id"] assert report_after["included_items"][0]["currency"] == item_before["currency"] assert report_after["selection_policy"] == "as_of" legacy_db = str(tmp_path / "legacy.sqlite3") service.create_asset_snapshot( "2026-06-30", "account", "legacy-account", "legacy", 10, "cash", db_path=legacy_db ) legacy = service.get_asset_snapshot_report("2026-06-30", db_path=legacy_db) legacy_item = legacy["included_items"][0] assert "valuation_id" not in legacy_item assert legacy_item["currency"] == "KRW"