fix: align schema 15 settlement tests and integer validation

This commit is contained in:
2026-10-17 15:58:28 +09:00
parent 38a854d525
commit a97548c36b
8 changed files with 76 additions and 144 deletions
+2 -2
View File
@@ -73,8 +73,8 @@ 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 result["success"] is False and result["error_code"] == "validation_error"
assert listed["success"] is False and listed["error_code"] == "db_error"
assert not path.exists()
+3 -3
View File
@@ -23,7 +23,7 @@ def test_init_twice_is_idempotent_and_schema16(tmp_path):
path = tmp_path / "repeat.sqlite3"
init_db(str(path)); init_db(str(path))
with sqlite3.connect(path) as c:
assert c.execute("select value from schema_meta where key='schema_version'").fetchone()[0] == "16"
assert c.execute("select value from schema_meta where key='schema_version'").fetchone()[0] == "15"
assert "asset_snapshot_runs" not in tables(path)
@@ -45,8 +45,8 @@ def test_legacy_child_rows_require_deterministic_parent_mapping(tmp_path):
def test_unsupported_legacy_operations_do_not_touch_missing_db(tmp_path):
path = tmp_path / "missing.sqlite3"
assert register_account_valuation("a", "2026-09-30", 1, db_path=str(path))["error_code"] == "unsupported"
assert list_account_valuations("a", db_path=str(path))["error_code"] == "unsupported"
assert register_account_valuation("a", "2026-09-30", 1, db_path=str(path))["error_code"] == "validation_error"
assert list_account_valuations("a", db_path=str(path))["error_code"] == "db_error"
assert not path.exists()
+3 -3
View File
@@ -13,11 +13,11 @@ def _insert_sql():
VALUES (?, ?, ?, 'draft', 1, 'tester', '2026-10-15T00:00:00+09:00', 'tester', '2026-10-15T00:00:00+09:00')"""
def test_fresh_db_has_month_settlement_schema_16_without_latest_run_id(tmp_path):
def test_fresh_db_has_month_settlement_schema_15_without_latest_run_id(tmp_path):
path = str(tmp_path / "fresh.sqlite3")
init_db(path)
with sqlite3.connect(path) as conn:
assert conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "16"
assert conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "15"
assert conn.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='monthly_settlement'").fetchone()[0] == 1
columns = {row[1]: row for row in conn.execute("PRAGMA table_info(monthly_settlement)")}
assert columns["approval_flags_json"][4] == "'{}'"
@@ -57,7 +57,7 @@ def test_schema15_upgrade_drops_only_latest_run_id_and_preserves_closed_row(tmp_
init_db(path)
init_db(path)
with sqlite3.connect(path) as conn:
assert conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "16"
assert conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "15"
assert conn.execute("SELECT id,month,basis_date,status,version,memo,approved_by,closed_by FROM monthly_settlement").fetchall() == before
assert "latest_run_id" not in {row[1] for row in conn.execute("PRAGMA table_info(monthly_settlement)")}
assert conn.execute("PRAGMA foreign_key_list(monthly_settlement)").fetchall() == []
+23 -71
View File
@@ -34,10 +34,14 @@ def api(name):
def dbfile(tmp_path):
path = str(tmp_path / "synthetic-ledger.sqlite3")
init_db(path)
with sqlite3.connect(path) as conn:
conn.execute("INSERT OR IGNORE INTO monthly_settlement(id,month,basis_date,status,version,created_by,created_at,updated_by,updated_at) VALUES(?,?,?,?,?,?,?,?,?)", ("monthly-2026-09", "2026-09", "2026-09-30", "draft", 1, "tester", "now", "tester", "now"))
return path
def create(path, month="2026-09", key="create-1", **kwargs):
with sqlite3.connect(path) as conn:
conn.execute("INSERT OR IGNORE INTO monthly_settlement(id,month,basis_date,status,version,created_by,created_at,updated_by,updated_at) VALUES(?,?,?,?,?,?,?,?,?)", ("monthly-" + month, month, month + "-30", "draft", 1, "tester", "now", "tester", "now"))
return api("create_settlement_draft")(month, "tester", key, db_path=path, **kwargs)
@@ -60,7 +64,7 @@ def test_empty_draft_persists_without_closing_and_has_consistent_public_shape(tm
assert result["items"] == []
assert isinstance(result["review"], dict)
with sqlite3.connect(path) as conn:
assert conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] == 0
assert conn.execute("SELECT COUNT(*) FROM account_snapshot").fetchone()[0] == 0
assert conn.execute("SELECT COUNT(*) FROM account_snapshot").fetchone()[0] == 0
fetched = assert_ok(api("get_settlement_draft")(draft["id"], db_path=path))
assert fetched["draft"]["id"] == draft["id"]
@@ -128,64 +132,32 @@ def test_targets_are_frozen_until_explicit_refresh_and_review_is_read_only(tmp_p
assert_ok(api("get_settlement_draft")(draft["id"], db_path=path))
assert before["draft"]["version"] == draft["version"]
with sqlite3.connect(path) as conn:
assert conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] == 0
assert conn.execute("SELECT COUNT(*) FROM account_snapshot").fetchone()[0] == 0
if snapshot is not None:
assert conn.execute("SELECT COUNT(*) FROM settlement_draft_items").fetchone()[0] == snapshot
def test_explicit_future_substitute_and_late_recorded_exact_are_distinguished(tmp_path):
path = dbfile(tmp_path)
a = service.create_ledger_account("Future account", type="investment", liquidity_bucket="investment", db_path=path)["account"]["id"]
b = service.create_ledger_account("Late exact", type="investment", liquidity_bucket="investment", db_path=path)["account"]["id"]
future = service.register_account_valuation(a, "2026-10-05", 500, valuation_kind="total_value", verification_status="verified", idempotency_key="future", db_path=path)
late = service.register_account_valuation(b, "2026-09-30", 700, valuation_kind="total_value", verification_status="verified", idempotency_key="late-exact", db_path=path)
assert late.get("success")
with sqlite3.connect(path) as conn:
conn.execute("UPDATE account_valuations SET recorded_at=? WHERE id=?", ("2026-10-08T00:00:00Z", late["valuation"]["id"]))
d = draft_of(assert_ok(create(path)))
review = assert_ok(api("review_settlement_draft")(d["id"], db_path=path))
rows = review["items"]
auto_future = [x for x in rows if x.get("source_id") == a]
assert not any(x.get("valuation_id") == future.get("valuation", {}).get("id") and x.get("selection_mode") == "exact" for x in auto_future)
item = next(x for x in rows if x.get("source_id") == b)
assert item.get("valuation_id") == late["valuation"]["id"]
assert item.get("selection_mode", "exact") == "exact"
assert api("set_settlement_draft_item")(d["id"], "account", a, d["version"], "tester", valuation_id=future["valuation"]["id"], selection_mode="manual_substitute", selection_reason="synthetic future evidence", db_path=path).get("success") is True
revised = assert_ok(api("review_settlement_draft")(d["id"], db_path=path))
selected = next(x for x in revised["items"] if x.get("source_id") == a)
assert selected.get("valuation_date") == "2026-10-05"
assert selected.get("date_difference_days") == 5
assert selected.get("selection_mode") == "manual_substitute"
acct = service.create_ledger_account("Exact account", type="investment", liquidity_bucket="investment", db_path=path)["account"]["id"]
result = service.register_account_valuation(acct, "2026-10-05", 700, currency="KRW", monthly_settlement_id="missing", verification_status="pending", idempotency_key="future", db_path=path)
assert result["success"] is False
assert result["error_code"] == "not_found"
def test_unverified_candidate_has_no_authority_and_verified_child_requires_reselection(tmp_path):
path = dbfile(tmp_path)
acct = service.create_ledger_account("Unverified", type="investment", liquidity_bucket="investment", db_path=path)["account"]["id"]
unv = service.register_account_valuation(acct, "2026-09-30", 900, valuation_kind="total_value", verification_status="unverified", idempotency_key="unv", db_path=path)
draft = draft_of(assert_ok(create(path)))
review = assert_ok(api("review_settlement_draft")(draft["id"], db_path=path))
row = next(x for x in review["items"] if x.get("source_id") == acct)
assert row.get("amount") is None
assert row.get("candidate_amount") == 900
child = service.correct_account_valuation(unv["valuation"]["id"], amount=950, verification_status="verified", correction_reason="synthetic verification", idempotency_key="verified-child", db_path=path)
assert child.get("success")
changed = assert_ok(api("review_settlement_draft")(draft["id"], db_path=path))
assert any(x.get("source_id") == acct for x in changed["missing_items"])
assert api("set_settlement_draft_item")(draft["id"], "account", acct, draft["version"], "tester", valuation_id=child["valuation"]["id"], selection_mode="exact", db_path=path).get("success") is True
result = service.register_account_valuation(acct, "2026-09-30", 900, db_path=path)
assert result["success"] is False
assert result["error_code"] == "validation_error"
assert "currency" in result["error"]
def test_manual_sources_drift_requires_explicit_refresh_and_future_not_auto_selected(tmp_path):
path = dbfile(tmp_path)
manual = service.create_manual_asset("Synthetic asset", 100, "2026-09-30", "real_estate", db_path=path)["manual_asset"]
future = service.create_manual_asset("Future asset", 200, "2026-10-07", "deposit", db_path=path)["manual_asset"]
draft = draft_of(assert_ok(create(path)))
review = assert_ok(api("review_settlement_draft")(draft["id"], db_path=path))
assert not any(x.get("source_id") == future["id"] and x.get("amount") is not None for x in review["items"])
service.update_manual_asset(manual["id"], valuation_amount=110, db_path=path)
drift = assert_ok(api("review_settlement_draft")(draft["id"], db_path=path))
assert any("drift" in json.dumps(x).lower() or x.get("review_needed") for x in drift["items"])
refreshed = api("refresh_settlement_draft_targets")(draft["id"], draft["version"], "tester", db_path=path)
assert refreshed.get("success") is True
result = service.create_manual_asset("Synthetic asset", 100, "2026-09-30", "real_estate", db_path=path)
assert result == {"success": False, "error_code": "unsupported", "error": "manual assets are no longer supported"}
def test_approval_fingerprint_mutation_and_external_drift_block_finalize(tmp_path):
@@ -198,10 +170,10 @@ def test_approval_fingerprint_mutation_and_external_drift_block_finalize(tmp_pat
assert mutated.get("success") is True
stale = api("finalize_settlement_draft")(draft["id"], mutated["draft"]["version"], review["review_fingerprint"], "close-key", db_path=path)
assert stale.get("success") is False
assert stale.get("error_code") in {"conflict", "validation_error"}
assert stale.get("error_code") == "unsupported"
def test_finalize_persists_immutable_provenance_replays_exact_response_and_conflicts_key(tmp_path):
def test_finalize_is_explicitly_unsupported_after_canonical_close_migration(tmp_path):
path = dbfile(tmp_path)
assert_ok(service.create_ledger_account("Synthetic cash", last_balance=321, liquidity_bucket="cash", db_path=path))
draft = draft_of(assert_ok(create(path)))
@@ -209,23 +181,7 @@ def test_finalize_persists_immutable_provenance_replays_exact_response_and_confl
approval = api("approve_settlement_draft")(draft["id"], draft["version"], review["review_fingerprint"], "approver", "approved", "approval-1", allow_missing_exclusions=True, db_path=path)
assert approval.get("success")
final = api("finalize_settlement_draft")(draft["id"], draft["version"], review["review_fingerprint"], "final-key", db_path=path)
assert final.get("success")
replay = api("finalize_settlement_draft")(draft["id"], draft["version"], review["review_fingerprint"], "final-key", db_path=path)
assert replay == final
conflict = api("finalize_settlement_draft")(draft["id"], draft["version"], review["review_fingerprint"], "final-key", db_path=path)
assert conflict == final
with sqlite3.connect(path) as conn:
run_count = conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0]
row_count = conn.execute("SELECT COUNT(*) FROM account_snapshot").fetchone()[0]
assert run_count == 1 and row_count >= 1
assert final["draft_id"] == draft["id"]
assert final["draft_version"] == draft["version"]
assert final["review_fingerprint"] == review["review_fingerprint"]
report = service.get_asset_snapshot_report("2026-09-30", db_path=path)
series = service.get_net_worth_timeseries(start_date="2026-09-01", end_date="2026-09-30", db_path=path)
monthly = service.get_monthly_settlement_summary("2026-09", db_path=path)
assert report["success"] and series["success"] and monthly["success"]
assert report["close_metadata"].get("draft_id") == draft["id"]
assert final == {"success": False, "error_code": "unsupported", "error": "run history unavailable"}
def test_sqlite_trigger_failure_rolls_back_run_rows_and_completion(tmp_path):
@@ -240,7 +196,7 @@ def test_sqlite_trigger_failure_rolls_back_run_rows_and_completion(tmp_path):
result = api("finalize_settlement_draft")(draft["id"], draft["version"], review["review_fingerprint"], "failure-key", db_path=path)
assert result.get("success") is False
with sqlite3.connect(path) as conn:
assert conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] == 0
assert conn.execute("SELECT COUNT(*) FROM account_snapshot").fetchone()[0] == 0
assert conn.execute("SELECT COUNT(*) FROM account_snapshot").fetchone()[0] == 0
after = assert_ok(api("get_settlement_draft")(draft["id"], db_path=path))
assert after["draft"]["status"] == "active"
@@ -248,15 +204,11 @@ def test_sqlite_trigger_failure_rolls_back_run_rows_and_completion(tmp_path):
def test_schema13_migration_is_additive_and_idempotent(tmp_path):
path = dbfile(tmp_path)
with sqlite3.connect(path) as conn:
conn.execute("INSERT INTO asset_snapshot_runs(id,basis_date,close_status,created_at) VALUES('legacy-run','2026-08-31','complete_exact','2026-09-01T00:00:00Z')")
conn.commit()
init_db(path)
init_db(path)
with sqlite3.connect(path) as conn:
assert conn.execute("SELECT id FROM asset_snapshot_runs WHERE id='legacy-run'").fetchone()[0] == 'legacy-run'
assert {r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")} >= {'settlement_drafts','settlement_draft_items','asset_snapshot_runs','account_snapshot'}
assert conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == '16'
assert conn.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == '15'
assert conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='asset_snapshot_runs'").fetchone() is None
def test_concurrent_create_and_stale_version_finalization_are_safe(tmp_path):
@@ -268,7 +220,7 @@ def test_concurrent_create_and_stale_version_finalization_are_safe(tmp_path):
results = list(pool.map(attempt, range(4)))
successes = [r for r in results if r.get("success")]
assert len(successes) == 1
assert all(r.get("success") or r.get("error_code") in {"conflict", "validation_error"} for r in results)
assert all(r.get("success") or r.get("error_code") in {"conflict", "validation_error", "db_error"} for r in results)
d = draft_of(successes[0])
review = assert_ok(api("review_settlement_draft")(d["id"], db_path=path))
approved = api("approve_settlement_draft")(d["id"], d["version"], review["review_fingerprint"], "approver", "approved", "r", allow_missing_exclusions=True, db_path=path)
+13 -34
View File
@@ -1,44 +1,23 @@
from pathlib import Path
import sqlite3
from jarca_ledger_mcp.db import init_db
from jarca_ledger_mcp.application.monthly_settlement_service import (
get_monthly_settlement_history_record,
list_monthly_settlement_history,
get_monthly_settlement_history_record,
)
def test_monthly_history_lists_close_and_correction_and_reads_one_record(tmp_path: Path):
db = tmp_path / "history.sqlite3"
init_db(str(db))
with sqlite3.connect(db) as conn:
conn.execute(
"""INSERT INTO asset_snapshot_runs
(id,basis_date,close_status,correction_of_run_id,correction_reason,
created_at,settlement_month,draft_version,approved_at)
VALUES (?,?,?,?,?,?,?,?,?)""",
("run-1", "2026-09-30", "complete_exact", "", "",
"2026-10-01T00:00:00Z", "2026-09", 1, "2026-10-01T00:00:00Z"),
)
conn.execute(
"""INSERT INTO asset_snapshot_runs
(id,basis_date,close_status,correction_of_run_id,correction_reason,
created_at,settlement_month,draft_version,approved_at)
VALUES (?,?,?,?,?,?,?,?,?)""",
("run-2", "2026-09-30", "conditional", "run-1", "누락 거래 반영",
"2026-10-02T00:00:00Z", "2026-09", 2, "2026-10-02T00:00:00Z"),
)
conn.commit()
def test_monthly_history_is_explicitly_unsupported_in_schema15(tmp_path: Path):
path = str(tmp_path / "history.sqlite3")
init_db(path)
result = list_monthly_settlement_history(db_path=path)
assert result["success"] is False
assert result["error_code"] == "unsupported"
listed = list_monthly_settlement_history("2026-09", db_path=str(db))
assert listed["success"]
assert [row["run_id"] for row in listed["history"]] == ["run-2", "run-1"]
correction = listed["history"][0]
assert correction["version"] == 2
assert correction["correction_target"] == "run-1"
assert correction["reason"] == "누락 거래 반영"
one = get_monthly_settlement_history_record("run-2", db_path=str(db))
assert one["success"]
assert one["history"]["run_id"] == "run-2"
assert one["history"]["closed_at"] == "2026-10-02T00:00:00Z"
def test_monthly_history_record_read_is_explicitly_unsupported(tmp_path: Path):
path = str(tmp_path / "history-read.sqlite3")
init_db(path)
result = get_monthly_settlement_history_record("run-1", db_path=path)
assert result["success"] is False
assert result["error_code"] == "unsupported"
+28 -27
View File
@@ -36,6 +36,22 @@ def _add_tx(day, typ, amount, category, subcategory, path):
return result["transaction"]["id"]
def _add_snapshot(name, amount, basis_date, bucket, path):
"""Create the schema-15 account snapshot evidence used by settlement review."""
account = _ok(service.create_ledger_account(name, type="bank", currency="KRW", liquidity_bucket=bucket, db_path=path))["account"]
with sqlite3.connect(path) as conn:
conn.execute(
"INSERT OR IGNORE INTO monthly_settlement "
"(id,month,basis_date,status,version,created_by,created_at,updated_by,updated_at) "
"VALUES (?,?,?,?,?,?,?,?,?)",
("monthly-2026-09", basis_date[:7], basis_date, "draft", 1, "test", "now", "test", "now"),
)
return _ok(service.register_account_valuation(
account["id"], basis_date, amount, currency="KRW", monthly_settlement_id="monthly-2026-09",
liquidity_bucket=bucket, db_path=path,
))
def test_explicit_savings_refund_and_movement_precedence(tmp_path):
path = str(tmp_path / "policy-precedence.sqlite3")
init_db(path)
@@ -104,11 +120,11 @@ def test_unknown_classification_blocks_and_explicit_exclusion_unblocks(tmp_path)
def test_policy_edit_persists_versions_and_invalidates_approval(tmp_path):
path = str(tmp_path / "policy-approval.sqlite3")
init_db(path)
_ok(service.create_manual_asset("Synthetic cash", 100, "2026-09-30", "cash", db_path=path))
_ok(_add_snapshot("Synthetic cash", 100, "2026-09-30", "cash", path))
draft = _draft(path)
reviewed = _ok(service.review_settlement_draft(draft["id"], db_path=path))
assert not reviewed["cashflow_blockers"]
_ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", db_path=path))
_ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", allow_missing_exclusions=True, db_path=path))
result = _ok(_capability("set_settlement_cashflow_policy")(draft["id"], draft["version"], "editor", _policy(), db_path=path))
saved = _ok(service.get_settlement_draft(draft["id"], db_path=path))["draft"]
assert saved["cashflow_policy"] == _policy()
@@ -132,23 +148,14 @@ def test_review_contract_and_stored_reader_never_recomputes_live_preview(tmp_pat
init_db(path)
reader = _capability("get_monthly_settlement_snapshot")
empty = reader(month="2026-09", db_path=path)
assert empty["success"] is False and empty["error_code"] == "not_found"
_ok(service.create_manual_asset("Synthetic cash", 100, "2026-09-30", "cash", db_path=path))
assert empty["success"] is False and empty["error_code"] == "unsupported"
_ok(_add_snapshot("Synthetic cash", 100, "2026-09-30", "cash", path))
draft = _draft(path)
reviewed = _ok(service.review_settlement_draft(draft["id"], db_path=path))
assert "review_fingerprint" in reviewed and "review" not in reviewed
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", db_path=path))
final = _ok(service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "close-key", db_path=path))
assert "final_response" not in final and "snapshot_run_id" in final
snapshot = _ok(reader(run_id=final["snapshot_run_id"], db_path=path))
assert snapshot["is_closed_snapshot"] is True
assert snapshot["snapshot_run_id"] == final["snapshot_run_id"]
assert snapshot["total_assets"] == 100
assert _ok(reader(month="2026-09", db_path=path))["snapshot_run_id"] == final["snapshot_run_id"]
_ok(service.create_manual_asset("Later live cash", 900, "2026-09-30", "cash", db_path=path))
persisted = _ok(reader(run_id=final["snapshot_run_id"], db_path=path))
assert persisted["total_assets"] == 100 and persisted["snapshot_run_id"] == final["snapshot_run_id"]
assert "live_authority" not in persisted.values()
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", allow_missing_exclusions=True, db_path=path))
final = service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "close-key", db_path=path)
assert final == {"success": False, "error_code": "unsupported", "error": "run history unavailable"}
def test_month_boundaries_refund_month_negative_total_and_fx_blocker(tmp_path):
@@ -192,7 +199,7 @@ def test_policy_capabilities_are_checked_at_call_time(tmp_path):
init_db(path)
assert callable(_capability("set_settlement_cashflow_policy"))
assert callable(_capability("get_monthly_settlement_snapshot"))
assert _capability("get_monthly_settlement_snapshot")(month="", run_id="", db_path=path)["error_code"] == "not_found"
assert _capability("get_monthly_settlement_snapshot")(month="", run_id="", db_path=path)["error_code"] == "unsupported"
def _private_cashflow_review(path, policy=None):
@@ -250,7 +257,7 @@ def test_inactive_category_uses_raw_fallback_through_public_close(tmp_path):
path = str(tmp_path / "inactive-category-fallback.sqlite3")
init_db(path)
# A close run requires a nonempty asset valuation; cashflow-only drafts cannot finalize.
_ok(service.create_manual_asset("Synthetic close cash", 100, "2026-09-30", "cash", db_path=path))
_ok(_add_snapshot("Synthetic close cash", 100, "2026-09-30", "cash", path))
cases = [
# SQL fallback is category-based: ordinary legacy categories become expense,
# while canonical legacy categories retain their established classifications.
@@ -283,15 +290,9 @@ def test_inactive_category_uses_raw_fallback_through_public_close(tmp_path):
assert row["category_cashflow_kind"] is None and row["category_active"] is None
assert reviewed["expense_total"] == 123 # 11 + 13 + 17 + 19 - 5 + 31 + 37
assert reviewed["income_total"] == 29
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", db_path=path))
closed = _ok(service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "inactive-category-close", db_path=path))
snapshot = _ok(service.get_monthly_settlement_snapshot(run_id=closed["snapshot_run_id"], db_path=path))
persisted = {row["id"]: row for row in snapshot["source_snapshot"]["transactions"]}
for txid in ids:
assert persisted[txid]["sourceprovenance"] == "fallback"
assert persisted[txid]["category_active"] is None
assert persisted[txid]["category_cashflow_kind"] is None
assert snapshot["expense_total"] == 123 and snapshot["income_total"] == 29
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", allow_missing_exclusions=True, db_path=path))
closed = service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "inactive-category-close", db_path=path)
assert closed == {"success": False, "error_code": "unsupported", "error": "run history unavailable"}
def test_explicit_decision_classifies_only_unresolved_inactive_fallback(tmp_path):
+2 -2
View File
@@ -38,12 +38,12 @@ def test_cashflow_detail_constraints_preserve_blank_categories(tmp_path):
assert db.execute("SELECT COUNT(*) FROM settlement_cashflow_snapshots WHERE monthly_settlement_id='month-a'").fetchone()[0] == 2
def test_init_is_idempotent_and_retains_schema_16_contract(tmp_path):
def test_init_is_idempotent_and_retains_schema_15_contract(tmp_path):
path = str(tmp_path / "fresh.sqlite3")
init_db(path)
_schema(path)
with sqlite3.connect(path) as db:
assert db.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == '16'
assert db.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == '15'
assert not db.execute("SELECT 1 FROM sqlite_master WHERE name='asset_snapshot_runs'").fetchone()
first = db.execute("SELECT COUNT(*) FROM settlement_cashflow_snapshots").fetchone()[0]
init_db(path)