fix: address settlement display and documentation review findings
Rework Issue 18 / PR 19, source review 76: C11-F01 and D02-F02/D03-F03/D04-F04. Preserve the approved contract and add named display, schema, policy, ownership and signature regressions.
This commit is contained in:
@@ -252,3 +252,99 @@ def test_authenticated_dashboard_renders_saved_comprehensive_snapshot(tmp_path,
|
||||
assert conn.execute("SELECT name, sql FROM sqlite_master ORDER BY type,name").fetchall() == before
|
||||
assert conn.execute("SELECT * FROM asset_snapshot_runs ORDER BY id").fetchall() == before_rows
|
||||
assert conn.execute("SELECT * FROM settlement_cashflow_snapshots ORDER BY id").fetchall() == before_detail
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
def test_draft_cashflow_blocker_renders_safe_transaction_id_and_reason(monkeypatch):
|
||||
from streamlit.testing.v1 import AppTest
|
||||
from jarca_ledger_mcp.adapters.streamlit import settlement_gateway
|
||||
|
||||
# Public review contract: cashflow_blockers is a list of safe dictionaries;
|
||||
# these fields are emitted by the service, not renderer-invented `code`.
|
||||
draft = {"id": "red-draft", "month": "2026-09", "basis_date": "2026-09-30",
|
||||
"status": "active", "version": 1, "cashflow_policy": {"savings_categories": [], "category_decisions": []}}
|
||||
blocker = {"transaction_id": "txn-safe-17", "reason": "unknown cashflow classification requires an explicit decision"}
|
||||
monkeypatch.setattr(settlement_gateway, "list_settlement_drafts",
|
||||
lambda *a, **k: {"success": True, "drafts": [draft]})
|
||||
monkeypatch.setattr(settlement_gateway, "get_settlement_draft",
|
||||
lambda *a, **k: {"success": True, "draft": draft, "items": [], "review": {"review_fingerprint": "fp"}})
|
||||
monkeypatch.setattr(settlement_gateway, "review_settlement_draft",
|
||||
lambda *a, **k: {"success": True, "version": 1, "review_fingerprint": "fp",
|
||||
"cashflow_blockers": [blocker], "cashflow_items": [], "missing_items": []})
|
||||
app = AppTest.from_string(
|
||||
"import streamlit as st\n"
|
||||
"from dashboard.page_views.settlement_drafts import render_settlement_drafts\n"
|
||||
"render_settlement_drafts(db_path='synthetic-unused.sqlite3', month='2026-09')",
|
||||
default_timeout=30,
|
||||
)
|
||||
app.run()
|
||||
assert not app.exception, f"synthetic renderer fixture failed before assertion: {app.exception}"
|
||||
next(button for button in app.button if button.label == "검토 / 새로고침 대상").click().run()
|
||||
assert not app.exception
|
||||
assert next(button for button in app.button if button.label == "검토 결과 승인").disabled
|
||||
shown = "\n".join(str(x.value) for x in list(app.warning) + list(app.error) + list(app.caption))
|
||||
assert "txn-safe-17" in shown and blocker["reason"] in shown, (
|
||||
"public renderer must show safe transaction_id and reason from each blocker; "
|
||||
"a failure here is the renderer dropping the declared public fields"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("comprehensive", [False, True])
|
||||
def test_saved_monthly_snapshot_renders_close_legacy_and_asset_evidence(monkeypatch, comprehensive):
|
||||
from streamlit.testing.v1 import AppTest
|
||||
from jarca_ledger_mcp.adapters.streamlit import overview_gateway, reconciliation_gateway, settlement_gateway
|
||||
|
||||
# Mirrors get_monthly_settlement_snapshot's declared response envelope/keys.
|
||||
# Legacy/partial record is read-only; excluded assets carry reason in `items`.
|
||||
snapshot = {"source": "settlement_snapshot", "run_id": "saved-run-9", "snapshot_run_id": "saved-run-9",
|
||||
"settlement_month": "2026-09", "basis_date": "2026-09-30", "is_closed_snapshot": True,
|
||||
"is_comprehensive": False, "legacy": True, "aggregation_version": None,
|
||||
"snapshot_schema_version": None, "close_status": "legacy_read_only", "cashflow_items": [],
|
||||
"items": [{"source_type": "manual_asset", "source_id": "asset-opaque-3", "name": "Synthetic asset",
|
||||
"exclusion_reason": "valuation_missing", "verification_status": "unverified"}],
|
||||
"source_snapshot": {"transactions": [], "assets": []}, "totals": {},
|
||||
"income_total": None, "expense_total": None, "net_cashflow": None,
|
||||
"total_assets": None, "total_liabilities": None, "net_worth": None,
|
||||
"correction": {"is_correction": False, "correction_of_run_id": "", "correction_reason": ""}}
|
||||
if comprehensive:
|
||||
snapshot.update(is_comprehensive=True, legacy=False, aggregation_version="comprehensive-v1",
|
||||
snapshot_schema_version=1, close_status="conditional")
|
||||
for key in ("income_total", "expense_total", "net_cashflow", "total_assets", "total_liabilities", "net_worth"):
|
||||
snapshot[key] = 0
|
||||
snapshot["totals"] = {key:snapshot[key] for key in ("income_total", "expense_total", "net_cashflow", "total_assets", "total_liabilities", "net_worth")}
|
||||
snapshot["source_snapshot"]["assets"] = [
|
||||
{"source_type":"account", "source_id":"missing-source-7", "status":"missing",
|
||||
"amount":None, "applied_amount":None, "valuation_id":None,
|
||||
"verification_status":"unverified", "exclusion_reason":""},
|
||||
{"source_type":"account", "source_id":"excluded-source-8", "status":"excluded",
|
||||
"amount":10, "applied_amount":None, "exclusion_reason":"synthetic exclusion",
|
||||
"unexpected_evidence":"not-public-evidence"},
|
||||
]
|
||||
monkeypatch.setattr(overview_gateway, "get_monthly_settlement_summary", lambda *a, **k: {"success": True, "month": "2026-09", "reports": {}})
|
||||
monkeypatch.setattr(overview_gateway, "list_cross_transfer_review_queue", lambda *a, **k: {"success": True, "items": []})
|
||||
monkeypatch.setattr(overview_gateway, "list_ledger_import_batches", lambda *a, **k: {"success": True, "import_batches": []})
|
||||
monkeypatch.setattr(overview_gateway, "get_asset_snapshot_report", lambda *a, **k: {"success": False})
|
||||
monkeypatch.setattr(reconciliation_gateway, "get_review_operating_indicators", lambda *a, **k: {"success": True, "indicators": {}})
|
||||
monkeypatch.setattr(reconciliation_gateway, "get_balance_reconciliation_report", lambda *a, **k: {"success": True, "items": []})
|
||||
monkeypatch.setattr(settlement_gateway, "get_monthly_settlement_snapshot", lambda *a, **k: {"success": True, **snapshot})
|
||||
monkeypatch.setattr(settlement_gateway, "list_settlement_drafts", lambda *a, **k: {"success": True, "drafts": []})
|
||||
app = AppTest.from_string(
|
||||
"from dashboard.page_views.monthly_settlement import page_monthly_settlement\n"
|
||||
"page_monthly_settlement('synthetic-unused.sqlite3')", default_timeout=30)
|
||||
app.run()
|
||||
assert not app.exception, f"synthetic renderer fixture failed before assertion: {app.exception}"
|
||||
import json
|
||||
body = "\n".join(str(x.value) for x in list(app.caption) + list(app.markdown) + list(app.text) + list(app.warning) + list(app.info))
|
||||
body += "\n" + json.dumps([frame.value.to_dict("records") for frame in app.dataframe], ensure_ascii=False, default=str)
|
||||
assert snapshot["close_status"] in body and (comprehensive or "legacy" in body.lower()), (
|
||||
"saved snapshot renderer must expose close_status and legacy/partial provenance"
|
||||
)
|
||||
assert "asset-opaque-3" in body and "valuation_missing" in body and "제외" in body, (
|
||||
"saved snapshot renderer must expose each missing/excluded asset status and reason"
|
||||
)
|
||||
if comprehensive:
|
||||
assert "missing-source-7" in body and "missing" in body and "누락" in body
|
||||
assert "excluded-source-8" in body and "synthetic exclusion" in body
|
||||
assert "not-public-evidence" not in body
|
||||
assert not app.exception
|
||||
|
||||
Reference in New Issue
Block a user