Files
jarca-ledger-mcp/tests/test_account_snapshot_linking.py

115 lines
5.4 KiB
Python

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"