fix: address account valuation review findings
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import inspect
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from jarca_ledger_mcp.application import account_valuation_service as valuation
|
||||
from jarca_ledger_mcp.application import (
|
||||
assets_service,
|
||||
categorization_services,
|
||||
cross_transfer_service,
|
||||
reconciliation_service,
|
||||
system_services,
|
||||
)
|
||||
from jarca_ledger_mcp.infrastructure.sqlite import connection
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def test_read_queries_missing_nested_db_return_stable_error_without_creating_path(tmp_path):
|
||||
db_path = tmp_path / "missing" / "nested" / "ledger.sqlite3"
|
||||
calls = [
|
||||
lambda: system_services.list_ledger_accounts(db_path=str(db_path)),
|
||||
lambda: valuation.list_account_valuations("account", db_path=str(db_path)),
|
||||
lambda: assets_service.list_manual_assets(db_path=str(db_path)),
|
||||
lambda: assets_service.preview_asset_snapshot_close(db_path=str(db_path)),
|
||||
lambda: reconciliation_service.get_balance_reconciliation_report(db_path=str(db_path)),
|
||||
]
|
||||
|
||||
for read in calls:
|
||||
result = read()
|
||||
assert result["success"] is False
|
||||
assert result["error_code"] == "db_not_initialized"
|
||||
assert result["error"]
|
||||
assert not db_path.exists()
|
||||
assert not db_path.parent.exists()
|
||||
|
||||
|
||||
def test_read_only_connection_rejects_writes_and_does_not_commit(tmp_path):
|
||||
db_path = tmp_path / "ledger.sqlite3"
|
||||
system_services.create_ledger_account("checking", db_path=str(db_path)) # first-use writer initializes
|
||||
before = _sha256(db_path)
|
||||
|
||||
with connection.read_db(str(db_path)) as conn:
|
||||
with pytest.raises(sqlite3.OperationalError):
|
||||
conn.execute("INSERT INTO accounts (id, name, type) VALUES ('x', 'x', 'bank')")
|
||||
|
||||
assert _sha256(db_path) == before
|
||||
|
||||
|
||||
def test_read_suite_does_not_initialize_or_change_initialized_database(tmp_path, monkeypatch):
|
||||
db_path = tmp_path / "ledger.sqlite3"
|
||||
system_services.create_ledger_account("checking", db_path=str(db_path))
|
||||
before = _sha256(db_path)
|
||||
def fail_init(*args, **kwargs):
|
||||
raise AssertionError("read path called init_db")
|
||||
|
||||
monkeypatch.setattr("jarca_ledger_mcp.infrastructure.sqlite.migrations.init_db", fail_init)
|
||||
monkeypatch.setattr("jarca_ledger_mcp.infrastructure.sqlite.connection.init_db", fail_init, raising=False)
|
||||
reads = [
|
||||
lambda: system_services.list_ledger_accounts(db_path=str(db_path)),
|
||||
lambda: valuation.list_account_valuations("account", db_path=str(db_path)),
|
||||
lambda: assets_service.list_manual_assets(db_path=str(db_path)),
|
||||
lambda: assets_service.preview_asset_snapshot_close(db_path=str(db_path)),
|
||||
lambda: reconciliation_service.get_balance_reconciliation_report(db_path=str(db_path)),
|
||||
]
|
||||
for read in reads:
|
||||
assert read()["success"] is True
|
||||
assert _sha256(db_path) == before
|
||||
|
||||
|
||||
def test_first_use_writer_still_initializes_database(tmp_path):
|
||||
db_path = tmp_path / "new" / "ledger.sqlite3"
|
||||
result = system_services.create_ledger_account("checking", db_path=str(db_path))
|
||||
assert result["success"] is True
|
||||
assert db_path.is_file()
|
||||
|
||||
|
||||
def test_read_schema_error_is_not_misreported_as_uninitialized(tmp_path):
|
||||
db_path = tmp_path / "empty.sqlite3"
|
||||
sqlite3.connect(db_path).close()
|
||||
|
||||
result = system_services.list_ledger_accounts(db_path=str(db_path))
|
||||
|
||||
assert result["success"] is False
|
||||
assert result["error_code"] == "db_error"
|
||||
|
||||
|
||||
def test_nested_categorization_preview_propagates_missing_db_envelope(tmp_path):
|
||||
db_path = tmp_path / "missing" / "ledger.sqlite3"
|
||||
|
||||
result = categorization_services.preview_categorization(db_path=str(db_path))
|
||||
|
||||
assert result["success"] is False
|
||||
assert result["error_code"] == "db_not_initialized"
|
||||
assert not db_path.parent.exists()
|
||||
|
||||
|
||||
def test_public_read_signatures_remain_positional_compatible():
|
||||
assert list(inspect.signature(valuation.list_account_valuations).parameters)[:2] == ["account_id", "include_rejected"]
|
||||
assert list(inspect.signature(assets_service.preview_asset_snapshot_close).parameters)[:2] == ["basis_date", "db_path"]
|
||||
assert list(inspect.signature(reconciliation_service.get_balance_reconciliation_report).parameters)[:2] == ["account_id", "baseline_balance"]
|
||||
|
||||
|
||||
def test_preview_categorization_initialized_db_preserves_success_envelope(tmp_path):
|
||||
db_path = tmp_path / "ledger.sqlite3"
|
||||
system_services.create_ledger_account("checking", db_path=str(db_path))
|
||||
result = categorization_services.preview_categorization(db_path=str(db_path))
|
||||
assert result["success"] is True
|
||||
assert result["matched_count"] == 0
|
||||
assert result["unmatched_count"] == 0
|
||||
|
||||
|
||||
def test_apply_categorization_rules_missing_db_initializes_writer_or_returns_stable_error(tmp_path):
|
||||
db_path = tmp_path / "nested" / "ledger.sqlite3"
|
||||
result = categorization_services.apply_categorization_rules(db_path=str(db_path))
|
||||
assert result["success"] is True
|
||||
assert result["preview"]["matched_count"] == 0
|
||||
|
||||
|
||||
def test_cross_transfer_review_queue_missing_nested_db_returns_stable_error_without_creating_path(tmp_path):
|
||||
db_path = tmp_path / "missing" / "nested" / "ledger.sqlite3"
|
||||
|
||||
result = cross_transfer_service.list_cross_transfer_review_queue(db_path=str(db_path))
|
||||
|
||||
assert result["success"] is False
|
||||
assert result["error_code"] == "db_not_initialized"
|
||||
assert result["error"]
|
||||
assert not db_path.exists()
|
||||
assert not db_path.parent.exists()
|
||||
|
||||
|
||||
def test_cross_transfer_preview_missing_nested_db_returns_stable_error_without_creating_path(tmp_path):
|
||||
db_path = tmp_path / "missing" / "nested" / "ledger.sqlite3"
|
||||
|
||||
result = cross_transfer_service.preview_cross_account_transfers(db_path=str(db_path))
|
||||
|
||||
assert result["success"] is False
|
||||
assert result["error_code"] == "db_not_initialized"
|
||||
assert result["error"]
|
||||
assert not db_path.exists()
|
||||
assert not db_path.parent.exists()
|
||||
|
||||
|
||||
def test_cross_transfer_read_paths_do_not_initialize_or_mutate_initialized_db(tmp_path, monkeypatch):
|
||||
db_path = tmp_path / "ledger.sqlite3"
|
||||
system_services.create_ledger_account("checking", db_path=str(db_path))
|
||||
before = _sha256(db_path)
|
||||
|
||||
def fail_init(*args, **kwargs):
|
||||
raise AssertionError("cross-transfer read path called init_db")
|
||||
|
||||
monkeypatch.setattr("jarca_ledger_mcp.infrastructure.sqlite.migrations.init_db", fail_init)
|
||||
monkeypatch.setattr("jarca_ledger_mcp.infrastructure.sqlite.connection.init_db", fail_init, raising=False)
|
||||
|
||||
queue = cross_transfer_service.list_cross_transfer_review_queue(db_path=str(db_path))
|
||||
preview = cross_transfer_service.preview_cross_account_transfers(db_path=str(db_path))
|
||||
|
||||
assert queue["success"] is True
|
||||
assert preview["success"] is True
|
||||
assert _sha256(db_path) == before
|
||||
Reference in New Issue
Block a user