from __future__ import annotations import inspect from pathlib import Path from dashboard.page_views import settlement_drafts as view from jarca_ledger_mcp import service from jarca_ledger_mcp.adapters.mcp import server, tool_contracts HISTORY_TOOLS = ( "list_monthly_settlement_history", "get_monthly_settlement_history_record", ) class _FakeStreamlit: def __init__(self) -> None: self.session_state: dict[str, object] = {} self.selectbox_calls: list[tuple[str, list[str], dict]] = [] self.captions: list[str] = [] self.json_values: list[object] = [] self.dataframes: list[object] = [] self.info_values: list[str] = [] self.error_values: list[str] = [] def selectbox(self, label, options, **kwargs): values = list(options) self.selectbox_calls.append((label, values, kwargs)) return values[-1] def caption(self, value): self.captions.append(str(value)) def json(self, value): self.json_values.append(value) def dataframe(self, value, **kwargs): self.dataframes.append(value) def info(self, value): self.info_values.append(str(value)) def error(self, value): self.error_values.append(str(value)) def test_history_tools_are_registered_with_public_no_db_path_schemas(): tools = server.mcp._tool_manager._tools for name in HISTORY_TOOLS: assert name in tool_contracts.TOOL_CONTRACTS assert name in tool_contracts.__all__ assert name in tools schema = tools[name].parameters if hasattr(tools[name], "parameters") else tools[name].inputSchema properties = schema.get("properties", {}) assert "db_path" not in properties assert set(properties) == set(inspect.signature(getattr(service, name)).parameters) - {"db_path"} def test_history_control_opens_selected_run_and_render_fetches_exact_record(monkeypatch): fake = _FakeStreamlit() monkeypatch.setattr(view, "st", fake) calls: list[tuple] = [] def list_history(month="", *, db_path=None): calls.append(("list", month, db_path)) return { "success": True, "history": [ {"run_id": "run-old", "settlement_month": month, "version": 2, "closed_at": "2026-10-01", "close_status": "complete_exact"}, {"run_id": "run-new", "settlement_month": month, "version": 3, "closed_at": "2026-10-02", "close_status": "conditional", "correction_target": "run-old", "reason": "fixed"}, ], } def get_record(run_id, *, db_path=None): calls.append(("get", run_id, db_path)) return {"success": True, "history": {"run_id": run_id, "net_cashflow": 55, "net_worth": 995}} monkeypatch.setattr(view.gateway, "list_monthly_settlement_history", list_history) monkeypatch.setattr(view.gateway, "get_monthly_settlement_history_record", get_record) view._open_settlement_history("draft-1", "run-old") assert fake.session_state["settlement_history_open_draft"] == "draft-1" assert fake.session_state["settlement_history_run_draft-1"] == "run-old" view._render_settlement_history("fixture.sqlite3", "2026-09", "draft-1") assert calls == [("list", "2026-09", "fixture.sqlite3"), ("get", "run-new", "fixture.sqlite3")] assert fake.selectbox_calls[0][0] == "불변 마감 run 선택" assert fake.selectbox_calls[0][1] == ["run-old", "run-new"] assert fake.json_values == [{"run_id": "run-new", "net_cashflow": 55, "net_worth": 995}] def test_public_history_contract_and_immutable_run_model_are_documented(): root = Path(__file__).parents[1] interface = (root / "docs/interface-contract.md").read_text(encoding="utf-8") data_model = (root / "docs/data-model.md").read_text(encoding="utf-8") for operation in HISTORY_TOOLS: assert operation in interface assert "MCP signatures omit internal `db_path`" in interface assert "마감내역보기" in interface assert "append-only immutable settlement history" in data_model assert "correction_of_run_id" in data_model