Files
jarca-ledger-mcp/tests/test_settlement_draft_dashboard.py
T

118 lines
5.6 KiB
Python

"""Dashboard lifecycle contract tests using Streamlit AppTest when available.
All data is synthetic; renders must not mutate/create state or databases. This
suite deliberately asserts the Issue 15 lifecycle UI is present before it exists.
"""
from __future__ import annotations
from pathlib import Path
import pytest
VIEW = Path(__file__).parents[1] / "dashboard" / "page_views" / "settlement_drafts.py"
def _app_wrapper(tmp_path: Path) -> Path:
wrapper = tmp_path / "settlement_panel_app.py"
wrapper.write_text(
"import os\nfrom dashboard.page_views.settlement_drafts import render_settlement_drafts\n"
"render_settlement_drafts(db_path=os.environ.get(\"JARCA_LEDGER_DB_PATH\"))\n",
encoding="utf-8",
)
return wrapper
def test_reusable_settlement_draft_panel_exists_and_is_importable(tmp_path):
assert VIEW.is_file(), "shared Dashboard settlement draft panel is missing"
from streamlit.testing.v1 import AppTest
app = AppTest.from_file(str(_app_wrapper(tmp_path)))
app.session_state["authenticated_actor"] = "fixture-operator"
app = app.run()
assert not app.exception
def test_monthly_and_assets_pages_invoke_shared_panel():
root = VIEW.parents[1]
pages = [p for p in root.rglob("*.py") if p.name in ("monthly_settlement.py", "assets.py")]
assert len(pages) == 2
for page in pages:
source = page.read_text(encoding="utf-8")
if page.name == "monthly_settlement.py":
assert "page_legacy_monthly_settlement" in source
source += (page.parent / "legacy_monthly_settlement.py").read_text(encoding="utf-8")
assert "render_settlement_drafts" in source
def test_streamlit_panel_renders_lifecycle_without_writing_or_creating_db(monkeypatch, tmp_path):
pytest.importorskip("streamlit.testing.v1")
from streamlit.testing.v1 import AppTest
db_path = tmp_path / "dashboard-missing.sqlite3"
# Explicit synthetic DB path and no authenticated actor: render only.
monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(db_path))
app = AppTest.from_file(str(_app_wrapper(tmp_path)))
app.session_state["authenticated_actor"] = "fixture-operator"
app = app.run()
assert not app.exception
page = "\n".join([e.value for e in app.markdown] + [e.value for e in app.caption] + [e.value for e in app.info] + [e.label for e in app.button])
for expected in ("초안", "검토", "승인", "최종", "취소", "정정", "평가자료", "제외 사유"):
assert expected in page
assert not db_path.exists(), "render-only Dashboard created a database"
def test_panel_actions_are_explicit_and_selection_state_bound_to_draft_version():
source = VIEW.read_text(encoding="utf-8")
for action in ("create_settlement_draft", "update_settlement_draft", "cancel_settlement_draft",
"refresh_settlement_draft_targets", "set_settlement_draft_item", "review_settlement_draft",
"approve_settlement_draft", "finalize_settlement_draft", "register_account_valuation",
"list_account_valuations", "correct_account_valuation"):
assert action in source, f"missing explicit action {action}"
assert "session_state" in source
assert "review_fingerprint" in source
assert "expected_version" in source or "version, actor" in source
assert "transaction" in source.lower() and "거래" in source
def test_closed_draft_view_exposes_immutable_evidence_and_correction_creation():
source = VIEW.read_text(encoding="utf-8")
for immutable in ("draft_version", "review_fingerprint", "final_run_id", "correction_of_run_id",
"basis_date", "created_by", "created_at", "updated_at", "memo"):
assert immutable in source, f"closed view omits {immutable}"
assert "correction_reason" in source
assert "completion" in source or "completed" in source
def test_render_path_contains_no_implicit_lifecycle_mutations():
source = VIEW.read_text(encoding="utf-8")
# All lifecycle mutations must be behind explicit submit/button branches;
# import/render must never invoke them unconditionally.
assert "if st.button" in source or "form_submit_button" in source
assert "render_settlement_drafts" in source
def test_app_test_create_review_approve_finalize_roundtrip(tmp_path, monkeypatch):
from streamlit.testing.v1 import AppTest
from jarca_ledger_mcp.db import init_db
from jarca_ledger_mcp.application.assets_service import create_manual_asset
db_path = tmp_path / "lifecycle.sqlite3"
init_db(str(db_path))
seeded = create_manual_asset("Synthetic asset", 125, "2026-09-30", "other", source_note="fixture", db_path=str(db_path))
assert seeded.get("success"), seeded
monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(db_path))
monkeypatch.setattr("dashboard.authentication.get_authenticated_actor", lambda: "fixture-operator")
app = AppTest.from_file(str(_app_wrapper(tmp_path)))
app.session_state["authenticated_actor"] = "fixture-operator"
app = app.run()
assert not app.exception
app.button[0].click().run()
assert not app.exception
next(button for button in app.button if button.label == "초안 만들기").click().run()
assert not app.exception
assert any(button.label == "검토 / 새로고침 대상" for button in app.button)
# Exercise read-only review through the rendered explicit button.
review = next(button for button in app.button if button.label == "검토 / 새로고침 대상")
review.click().run(timeout=10)
assert not app.exception
assert any("review_fingerprint" in item.value for item in app.caption)
assert not app.exception