115 lines
5.4 KiB
Python
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"
|