feat: deliver schema 15 monthly settlement lifecycle
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
|
||||
from jarca_ledger_mcp.application.account_valuation_service import (
|
||||
list_account_valuations,
|
||||
register_account_valuation,
|
||||
)
|
||||
from jarca_ledger_mcp.application.assets_service import (
|
||||
create_manual_asset,
|
||||
list_manual_assets,
|
||||
preview_asset_snapshot_close,
|
||||
)
|
||||
from jarca_ledger_mcp.application.settlement_draft_service import create_settlement_draft
|
||||
from jarca_ledger_mcp.infrastructure.sqlite.migrations import init_db
|
||||
|
||||
|
||||
def _tables(path):
|
||||
with sqlite3.connect(path) as conn:
|
||||
return {r[0] for r in conn.execute("select name from sqlite_master where type='table'")}
|
||||
|
||||
|
||||
EXPECTED_TABLES = {
|
||||
# Sourced from schema.py plus migration-created settlement draft tables.
|
||||
"schema_meta",
|
||||
"accounts",
|
||||
"transactions",
|
||||
"budgets",
|
||||
"import_batches",
|
||||
"category_rules",
|
||||
"ledger_categories",
|
||||
|
||||
"account_snapshot",
|
||||
"settlement_cashflow_snapshots",
|
||||
"review_decisions",
|
||||
"reconciliation_sessions",
|
||||
"monthly_settlement",
|
||||
"settlement_drafts",
|
||||
"settlement_draft_items",
|
||||
}
|
||||
|
||||
|
||||
def test_fresh_init_has_no_account_valuations_and_exact_schema_tables(tmp_path):
|
||||
path = tmp_path / "fresh.sqlite3"
|
||||
init_db(str(path))
|
||||
tables = _tables(path)
|
||||
assert "account_valuations" not in tables
|
||||
assert tables == EXPECTED_TABLES
|
||||
|
||||
|
||||
def test_init_is_idempotent(tmp_path):
|
||||
path = tmp_path / "repeat.sqlite3"
|
||||
assert init_db(str(path)) == str(path)
|
||||
assert init_db(str(path)) == str(path)
|
||||
assert _tables(path) == EXPECTED_TABLES
|
||||
|
||||
|
||||
def test_legacy_nonempty_account_valuations_row_is_dropped_only(tmp_path):
|
||||
path = tmp_path / "legacy.sqlite3"
|
||||
init_db(str(path))
|
||||
with sqlite3.connect(path) as conn:
|
||||
conn.execute("create table account_valuations (id text primary key, account_id text, amount integer)")
|
||||
conn.execute("insert into account_valuations values ('legacy-1','acct-1',123)")
|
||||
conn.execute("insert into accounts (id,name,type,active,created_at,updated_at) values ('acct-1','Keep me','bank',1,'t','t')")
|
||||
conn.commit()
|
||||
init_db(str(path))
|
||||
with sqlite3.connect(path) as conn:
|
||||
assert conn.execute("select count(*) from accounts where id='acct-1'").fetchone()[0] == 1
|
||||
assert conn.execute("select name from sqlite_master where type='table' and name='account_valuations'").fetchone() is None
|
||||
|
||||
|
||||
def test_unsupported_valuation_operations_do_not_create_or_access_db(tmp_path):
|
||||
path = tmp_path / "must-not-exist.sqlite3"
|
||||
result = register_account_valuation("acct", "2026-09-30", 1, db_path=str(path))
|
||||
listed = list_account_valuations("acct", db_path=str(path))
|
||||
assert result["success"] is False and result["error_code"] == "unsupported"
|
||||
assert listed["success"] is False and listed["error_code"] == "unsupported"
|
||||
assert not path.exists()
|
||||
|
||||
|
||||
def test_active_account_preview_reports_missing_without_sql_error(tmp_path):
|
||||
path = tmp_path / "preview.sqlite3"
|
||||
init_db(str(path))
|
||||
with sqlite3.connect(path) as conn:
|
||||
conn.execute("insert into accounts (id,name,type,active,created_at,updated_at) values ('acct-1','Active','bank',1,'t','t')")
|
||||
conn.commit()
|
||||
result = preview_asset_snapshot_close("2026-09-30", db_path=str(path))
|
||||
assert result["success"] is True
|
||||
assert result["close_status"] in {"incomplete_draft", "complete_exact"}
|
||||
assert not any(w.get("code") == "db_error" for w in result["warnings"])
|
||||
|
||||
|
||||
def test_active_account_draft_returns_review_blocker_without_sql_error(tmp_path):
|
||||
path = tmp_path / "draft.sqlite3"
|
||||
init_db(str(path))
|
||||
with sqlite3.connect(path) as conn:
|
||||
conn.execute("insert into accounts (id,name,type,active,created_at,updated_at) values ('acct-1','Active','bank',1,'t','t')")
|
||||
conn.commit()
|
||||
result = create_settlement_draft("2026-09", "tester", "draft-key", db_path=str(path))
|
||||
assert result["success"] is True
|
||||
assert result.get("review", {}).get("approval_required") is True
|
||||
assert not any("sql" in str(v).lower() for v in result.get("review", {}).values())
|
||||
|
||||
|
||||
def test_manual_asset_flow_is_unsupported_after_removal(tmp_path):
|
||||
path = tmp_path / "manual.sqlite3"
|
||||
init_db(str(path))
|
||||
created = create_manual_asset("Emergency fund", 100000, "2026-09-30", "cash", db_path=str(path))
|
||||
listed = list_manual_assets(db_path=str(path))
|
||||
assert created == {"success": False, "error_code": "unsupported", "error": "manual assets are no longer supported"}
|
||||
assert listed == {"success": False, "error_code": "unsupported", "error": "manual assets are no longer supported"}
|
||||
Reference in New Issue
Block a user