from __future__ import annotations import sqlite3 import pytest from jarca_ledger_mcp.application.monthly_settlement_service import ( create_monthly_settlement, link_account_snapshot, list_account_snapshots_for_settlement, ) from jarca_ledger_mcp.infrastructure.sqlite.migrations import init_db def _account(conn, ident="acct-1", name="실제계정", currency="KRW"): conn.execute( "INSERT INTO accounts(id,name,currency,created_at,updated_at) VALUES(?,?,?,?,?)", (ident, name, currency, "2026-10-01T00:00:00Z", "2026-10-01T00:00:00Z"), ) def _snapshot(conn, ident, *, account="acct-1", basis_date="2026-10-31", currency="KRW", settlement=None, amount=1234): conn.execute( """INSERT INTO account_snapshot (id,basis_date,source_type,source_id,name,amount,currency,liquidity_bucket, monthly_settlement_id,valuation_id,verification_status,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)""", (ident, basis_date, "account", account, "원본 스냅샷명", amount, currency, "cash", settlement, "valuation-1", "unverified", "2026-10-31T00:00:00Z"), ) def _ledger(tmp_path): path = tmp_path / "link.sqlite3" init_db(str(path)) with sqlite3.connect(path) as conn: draft = create_monthly_settlement("2026-10", db_path=str(path))["settlement_id"] conn.execute("UPDATE monthly_settlement SET basis_date='2026-10-31' WHERE id=?", (draft,)) _account(conn) conn.commit() return path, draft def test_link_updates_only_monthly_fk_and_preserves_snapshot_fields(tmp_path): path, settlement = _ledger(tmp_path) with sqlite3.connect(path) as conn: _snapshot(conn, "candidate") before = conn.execute("SELECT * FROM account_snapshot WHERE id='candidate'").fetchone() columns = [row[1] for row in conn.execute("PRAGMA table_info(account_snapshot)")] result = link_account_snapshot("candidate", settlement, db_path=str(path)) assert result["success"] is True with sqlite3.connect(path) as conn: after = conn.execute("SELECT * FROM account_snapshot WHERE id='candidate'").fetchone() assert len(before) == len(after) == len(columns) changed = {column: (old, new) for column, old, new in zip(columns, before, after) if old != new} assert changed == {"monthly_settlement_id": (None, settlement)} def test_actual_read_function_returns_unlinked_candidates_in_exact_order(tmp_path): path, settlement = _ledger(tmp_path) with sqlite3.connect(path) as conn: _snapshot(conn, "old", basis_date="2026-09-30", amount=1) _snapshot(conn, "exact-old-created", basis_date="2026-10-31", amount=2) _snapshot(conn, "exact-new-created", basis_date="2026-10-31", amount=3) conn.execute("UPDATE account_snapshot SET created_at='2026-10-31T02:00:00Z' WHERE id='exact-old-created'") conn.execute("UPDATE account_snapshot SET created_at='2026-10-31T03:00:00Z' WHERE id='exact-new-created'") conn.commit() result = list_account_snapshots_for_settlement(settlement, db_path=str(path)) row = result["snapshots"][0] assert [item["snapshot_id"] for item in row["unlinked_candidates"]] == ["exact-new-created", "exact-old-created", "old"] @pytest.mark.parametrize("status", ["approved", "closed"]) def test_link_rejects_approved_and_closed(tmp_path, status): path, settlement = _ledger(tmp_path) with sqlite3.connect(path) as conn: _snapshot(conn, "candidate") conn.execute("""UPDATE monthly_settlement SET status=?, approved_by='test', approved_at='2026-10-31T00:00:00Z', approved_version=1, approved_fingerprint='test', closed_by='test', closed_at='2026-10-31T00:00:00Z' WHERE id=?""", (status, settlement)) conn.commit() result = link_account_snapshot("candidate", settlement, db_path=str(path)) assert result["success"] is False assert result["error_code"] == "invalid_settlement_status" def test_link_rejects_unrelated_month_and_already_linked_snapshot(tmp_path): path, settlement = _ledger(tmp_path) with sqlite3.connect(path) as conn: other = create_monthly_settlement("2026-11", db_path=str(path))["settlement_id"] _snapshot(conn, "linked-other", settlement=other) _snapshot(conn, "linked-this", settlement=settlement) conn.commit() assert link_account_snapshot("linked-other", settlement, db_path=str(path))["error_code"] == "already_linked" assert link_account_snapshot("linked-this", settlement, db_path=str(path))["error_code"] == "already_linked" def test_basis_date_mismatch_requires_explicit_true(tmp_path): path, settlement = _ledger(tmp_path) with sqlite3.connect(path) as conn: _snapshot(conn, "mismatch", basis_date="2026-09-30") conn.commit() rejected = link_account_snapshot("mismatch", settlement, db_path=str(path)) assert rejected["error_code"] == "basis_date_mismatch" accepted = link_account_snapshot("mismatch", settlement, allow_basis_date_mismatch=True, db_path=str(path)) assert accepted["success"] is True def test_link_rejects_account_currency_mismatch(tmp_path): path, settlement = _ledger(tmp_path) with sqlite3.connect(path) as conn: _snapshot(conn, "bad-currency", currency="USD") conn.commit() result = link_account_snapshot("bad-currency", settlement, db_path=str(path)) assert result["error_code"] == "validation_error"