import sqlite3 from jarca_ledger_mcp import service from jarca_ledger_mcp.db import init_db def _account(db_path, currency="KRW"): result = service.create_ledger_account("주계좌", currency=currency, db_path=db_path) assert result["success"] return result["account"]["id"] def test_account_valuation_schema_is_v12_and_idempotent(tmp_path): path = str(tmp_path / "ledger.sqlite3") init_db(path) init_db(path) with sqlite3.connect(path) as conn: cols = {row[1] for row in conn.execute("PRAGMA table_info(account_valuations)")} version = conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] assert version == "12" assert {"id", "account_id", "basis_date", "amount", "currency", "valuation_kind", "source_type", "source_ref", "verification_status", "recorded_at", "memo", "idempotency_key", "supersedes_id", "correction_reason", "as_of_at", "timezone", "cost_basis", "net_contributed", "reported_principal_kind", "reported_principal_label", "profit", "return_rate", "return_basis", "principal", "interest", "fee", "representative_liability_definition"} <= cols def test_register_same_idempotency_payload_reuses_row_and_changed_payload_conflicts(tmp_path): path = str(tmp_path / "ledger.sqlite3") account_id = _account(path) first = service.register_account_valuation(account_id, "2026-06-30", 1000, idempotency_key="sync-1", verification_status="verified", source_type="import", source_ref="stmt-1", db_path=path) same = service.register_account_valuation(account_id, "2026-06-30", 1000, idempotency_key="sync-1", verification_status="verified", source_type="import", source_ref="stmt-1", db_path=path) conflict = service.register_account_valuation(account_id, "2026-06-30", 1200, idempotency_key="sync-1", verification_status="verified", source_type="import", source_ref="stmt-1", db_path=path) assert first["success"] and same["success"] assert same["reused"] is True and same["valuation"]["id"] == first["valuation"]["id"] assert conflict["success"] is False and conflict["error_code"] == "idempotency_conflict" def test_select_exact_and_as_of_excludes_unverified_and_reports_carry_forward(tmp_path): path = str(tmp_path / "ledger.sqlite3") account_id = _account(path) service.register_account_valuation(account_id, "2026-06-01", 1000, verification_status="verified", idempotency_key="v1", db_path=path) service.register_account_valuation(account_id, "2026-06-15", 2000, verification_status="unverified", idempotency_key="v2", db_path=path) exact = service.select_account_valuation(account_id, "2026-06-15", db_path=path) as_of = service.select_account_valuation(account_id, "2026-06-20", as_of=True, db_path=path) assert exact["success"] and exact["valuation"] is None assert exact["warning"] == "unverified_candidate" assert as_of["success"] and as_of["valuation"]["amount"] == 1000 assert as_of["carried_forward"] is True and as_of["valuation_age_days"] == 19 def test_correction_is_append_only_reasoned_and_single_child(tmp_path): path = str(tmp_path / "ledger.sqlite3") account_id = _account(path) original = service.register_account_valuation(account_id, "2026-06-30", 1000, verification_status="verified", idempotency_key="v1", db_path=path) corrected = service.correct_account_valuation(original["valuation"]["id"], 1200, correction_reason="은행 원장 정정", idempotency_key="v2", db_path=path) duplicate = service.correct_account_valuation(original["valuation"]["id"], 1300, correction_reason="두 번째 분기", idempotency_key="v3", db_path=path) listed = service.list_account_valuations(account_id, db_path=path) assert corrected["success"] and corrected["valuation"]["supersedes_id"] == original["valuation"]["id"] assert duplicate["success"] is False and duplicate["error_code"] == "correction_conflict" assert listed["count"] == 2 selected = service.select_account_valuation(account_id, "2026-06-30", db_path=path) assert selected["valuation"]["amount"] == 1200 def test_account_balance_write_records_unverified_candidate_only_with_real_basis_date(tmp_path): path = str(tmp_path / "ledger.sqlite3") created = service.create_ledger_account("동기화 계좌", last_balance=500, last_synced_at="2026-06-30", db_path=path) service.create_ledger_account("날짜 없는 계좌", last_balance=700, db_path=path) values = service.list_account_valuations(created["account"]["id"], db_path=path) assert values["count"] == 1 assert values["valuations"][0]["verification_status"] == "unverified" assert values["valuations"][0]["basis_date"] == "2026-06-30" def test_idempotency_scope_includes_basis_and_correction_metadata(tmp_path): path = str(tmp_path / "ledger.sqlite3") account_id = _account(path) first = service.register_account_valuation(account_id, "2026-06-30", 1000, idempotency_key="same", db_path=path) changed_basis = service.register_account_valuation(account_id, "2026-07-01", 1000, idempotency_key="same", db_path=path) changed_reason = service.register_account_valuation(account_id, "2026-06-30", 1000, idempotency_key="same", supersedes_id="other", correction_reason="정정", db_path=path) assert first["success"] assert changed_basis["error_code"] == "idempotency_conflict" assert changed_reason["error_code"] == "idempotency_conflict" def test_correction_preserves_optional_fields_unless_overridden(tmp_path): path = str(tmp_path / "ledger.sqlite3") account_id = _account(path, currency="USD") original = service.register_account_valuation(account_id, "2026-06-30", 1000, currency="USD", cost_basis=900, net_contributed=950, profit=50, return_rate=0.055, principal=800, interest=20, fee=5, as_of_at="2026-06-30T12:00:00+09:00", timezone="Asia/Seoul", db_path=path) corrected = service.correct_account_valuation(original["valuation"]["id"], 1100, correction_reason="정정", idempotency_key="corr", profit=200, db_path=path) assert corrected["success"] value = corrected["valuation"] assert value["cost_basis"] == 900 and value["net_contributed"] == 950 assert value["profit"] == 200 and value["return_rate"] == 0.055 assert value["principal"] == 800 and value["interest"] == 20 and value["fee"] == 5 assert value["as_of_at"] == "2026-06-30T12:00:00+09:00" and value["timezone"] == "Asia/Seoul" def test_account_name_only_update_does_not_create_candidate(tmp_path): path = str(tmp_path / "ledger.sqlite3") created = service.create_ledger_account("계좌", last_balance=500, last_synced_at="2026-06-30", db_path=path) account_id = created["account"]["id"] updated = service.update_ledger_account(account_id, name="새 이름", db_path=path) values = service.list_account_valuations(account_id, db_path=path) assert updated["success"] and values["count"] == 1