feat: add persistent monthly settlement drafts and approved valuation selection
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"""Contract tests for the Issue 15 Streamlit gateway and MCP surface.
|
||||
|
||||
Fixtures are synthetic and isolated: each test receives an empty temporary SQLite
|
||||
path; no production DB or external service is used. These assertions intentionally
|
||||
fail until the approved adapter/API is implemented.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
OPERATIONS = {
|
||||
"create_settlement_draft": ["month", "created_by", "idempotency_key", "basis_date", "memo", "correction_of_run_id", "correction_reason", "db_path"],
|
||||
"list_settlement_drafts": ["month", "db_path"],
|
||||
"get_settlement_draft": ["draft_id", "db_path"],
|
||||
"update_settlement_draft": ["draft_id", "expected_version", "updated_by", "memo", "db_path"],
|
||||
"refresh_settlement_draft_targets": ["draft_id", "expected_version", "updated_by", "db_path"],
|
||||
"set_settlement_draft_item": ["draft_id", "source_type", "source_id", "expected_version", "selected_by", "valuation_id", "selection_mode", "selection_reason", "included", "exclusion_reason", "db_path"],
|
||||
"review_settlement_draft": ["draft_id", "db_path"],
|
||||
"approve_settlement_draft": ["draft_id", "expected_version", "review_fingerprint", "approved_by", "approval_reason", "approval_reference", "allow_missing_exclusions", "allow_carried_before", "allow_manual_substitute", "db_path"],
|
||||
"finalize_settlement_draft": ["draft_id", "expected_version", "review_fingerprint", "idempotency_key", "db_path"],
|
||||
"cancel_settlement_draft": ["draft_id", "expected_version", "cancelled_by", "reason", "db_path"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db_path(tmp_path: Path) -> Path:
|
||||
return tmp_path / "synthetic-settlement.sqlite3"
|
||||
|
||||
|
||||
def test_gateway_exports_exact_contract_signature_and_delegates(monkeypatch, db_path):
|
||||
module = importlib.import_module("jarca_ledger_mcp.adapters.streamlit.settlement_gateway")
|
||||
service = importlib.import_module("jarca_ledger_mcp.application.settlement_draft_service")
|
||||
monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(db_path))
|
||||
calls = []
|
||||
for name, expected_parameters in OPERATIONS.items():
|
||||
gateway_fn = getattr(module, name)
|
||||
service_fn = getattr(service, name)
|
||||
assert list(inspect.signature(gateway_fn).parameters) == expected_parameters
|
||||
assert inspect.signature(gateway_fn) == inspect.signature(service_fn)
|
||||
marker = {"success": True, "operation": name}
|
||||
def stub(*args, _marker=marker, _name=name, **kwargs):
|
||||
calls.append((_name, kwargs.get("db_path")))
|
||||
return _marker
|
||||
monkeypatch.setattr(service, name, stub)
|
||||
kwargs = {p: "synthetic" for p in expected_parameters}
|
||||
kwargs.update(db_path=str(db_path))
|
||||
kwargs["correction_of_run_id"] = ""
|
||||
kwargs["correction_reason"] = ""
|
||||
if "expected_version" in kwargs:
|
||||
kwargs["expected_version"] = 1
|
||||
if "included" in kwargs:
|
||||
kwargs["included"] = True
|
||||
if name == "create_settlement_draft":
|
||||
kwargs.update(month="2026-09", basis_date="2026-09-30")
|
||||
assert gateway_fn(**kwargs) == marker
|
||||
assert calls[-1] == (name, str(db_path))
|
||||
|
||||
|
||||
def test_gateway_exposes_existing_valuation_evidence_operations():
|
||||
gateway = importlib.import_module("jarca_ledger_mcp.adapters.streamlit.settlement_gateway")
|
||||
for name in ("register_account_valuation", "list_account_valuations", "correct_account_valuation"):
|
||||
assert callable(getattr(gateway, name)), name
|
||||
|
||||
|
||||
def test_mcp_contract_and_fastmcp_tool_calls_preserve_service_result(monkeypatch, db_path):
|
||||
contracts = importlib.import_module("jarca_ledger_mcp.adapters.mcp.tool_contracts")
|
||||
server = importlib.import_module("jarca_ledger_mcp.adapters.mcp.server")
|
||||
monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(db_path))
|
||||
calls = []
|
||||
tools = getattr(server, "mcp", getattr(server, "server", None))._tool_manager._tools
|
||||
for name in OPERATIONS:
|
||||
assert name in contracts.TOOL_CONTRACTS
|
||||
assert name in tools
|
||||
marker = {"success": True, "draft": {"id": "fixture-draft", "version": 1}}
|
||||
def stub(*args, _marker=marker, _name=name, **kwargs):
|
||||
calls.append(_name)
|
||||
return _marker
|
||||
monkeypatch.setattr(contracts, name, stub)
|
||||
tool = tools[name]
|
||||
call = getattr(tool, "fn", getattr(tool, "function", None))
|
||||
assert call is not None
|
||||
kwargs = {p: "synthetic" for p in OPERATIONS[name]}
|
||||
kwargs.pop("db_path", None)
|
||||
if "expected_version" in kwargs: kwargs["expected_version"] = 1
|
||||
if "included" in kwargs: kwargs["included"] = True
|
||||
if name == "create_settlement_draft": kwargs.update(month="2026-09", basis_date="2026-09-30")
|
||||
assert call(**kwargs) == marker
|
||||
assert calls[-1] == name
|
||||
|
||||
|
||||
def test_read_only_gateway_calls_do_not_create_missing_database(monkeypatch, db_path):
|
||||
gateway = importlib.import_module("jarca_ledger_mcp.adapters.streamlit.settlement_gateway")
|
||||
# Real delegation is required here: read paths must not create a missing file.
|
||||
for name in ("list_settlement_drafts",):
|
||||
getattr(gateway, name)(db_path=str(db_path))
|
||||
assert not db_path.exists()
|
||||
|
||||
|
||||
|
||||
def test_dashboard_authentication_entrypoint_precedes_database_resolution():
|
||||
app_source = (Path(__file__).parents[1] / "dashboard" / "app.py").read_text(encoding="utf-8")
|
||||
assert app_source.index("require_dashboard_authentication()") < app_source.index("get_db_path()")
|
||||
|
||||
|
||||
def test_service_reexports_every_lifecycle_operation():
|
||||
service = importlib.import_module("jarca_ledger_mcp.application.settlement_draft_service")
|
||||
assert all(callable(getattr(service, name, None)) for name in OPERATIONS)
|
||||
Reference in New Issue
Block a user