68 lines
4.3 KiB
Python
68 lines
4.3 KiB
Python
"""Acceptance contract for immutable, reason-linked settlement corrections."""
|
|
import sqlite3
|
|
|
|
from jarca_ledger_mcp import service as s
|
|
from jarca_ledger_mcp.db import init_db
|
|
|
|
|
|
def ok(r):
|
|
assert r['success'], r
|
|
return r
|
|
|
|
|
|
def counts(path):
|
|
with sqlite3.connect(path) as c:
|
|
return {table: c.execute(f'SELECT * FROM {table} ORDER BY rowid').fetchall()
|
|
for table in ('asset_snapshot_runs', 'asset_snapshots', 'settlement_drafts', 'settlement_draft_items')}
|
|
|
|
|
|
def finish(path, draft):
|
|
review = ok(s.review_settlement_draft(draft['id'], db_path=path))
|
|
approved = ok(s.approve_settlement_draft(draft['id'], draft['version'], review['review_fingerprint'], 'synthetic reviewer', 'approved synthetic correction', 'acceptance', db_path=path))
|
|
return ok(s.finalize_settlement_draft(draft['id'], approved['draft']['version'], review['review_fingerprint'], 'close-' + draft['id'], db_path=path))
|
|
|
|
|
|
def test_correction_links_reason_preserves_prior_rows_and_replays_after_later_correction(tmp_path):
|
|
path = str(tmp_path / 'correction.sqlite3')
|
|
init_db(path)
|
|
aid = ok(s.create_ledger_account('Synthetic cash', db_path=path))['account']['id']
|
|
first_value = ok(s.register_account_valuation(aid, '2026-09-30', 100, verification_status='verified', idempotency_key='v1', db_path=path))['valuation']['id']
|
|
original_draft = ok(s.create_settlement_draft('2026-09', 'synthetic', 'draft-1', db_path=path))['draft']
|
|
original = finish(path, original_draft)
|
|
original_rows = counts(path)
|
|
corrected_value = ok(s.correct_account_valuation(first_value, 125, 'synthetic valuation correction', idempotency_key='v2', db_path=path))['valuation']['id']
|
|
assert counts(path)['asset_snapshot_runs'] == original_rows['asset_snapshot_runs']
|
|
assert counts(path)['asset_snapshots'] == original_rows['asset_snapshots']
|
|
|
|
run_id = original['snapshot_run_id']
|
|
bad_requests = [('', 'reason'), (run_id, ''), ('does-not-exist', 'reason')]
|
|
before = counts(path)
|
|
for i, (parent, reason) in enumerate(bad_requests):
|
|
result = s.create_settlement_draft('2026-09', 'synthetic', f'invalid-{i}', correction_of_run_id=parent, correction_reason=reason, db_path=path)
|
|
assert not result['success']
|
|
assert counts(path) == before
|
|
ordinary = s.create_settlement_draft('2026-09', 'synthetic', 'ordinary-after-close', db_path=path)
|
|
assert not ordinary['success'] and ordinary['error_code'] == 'validation_error'
|
|
assert counts(path) == before
|
|
|
|
correction = ok(s.create_settlement_draft('2026-09', 'synthetic', 'correction-1', correction_of_run_id=run_id, correction_reason='synthetic close correction', db_path=path))['draft']
|
|
active_conflict = s.create_settlement_draft('2026-09', 'synthetic', 'active-conflict', correction_of_run_id=run_id, correction_reason='another linked correction', db_path=path)
|
|
assert not active_conflict['success'] and active_conflict['error_code'] == 'conflict'
|
|
assert counts(path)['settlement_drafts'] == before['settlement_drafts'] + [counts(path)['settlement_drafts'][-1]]
|
|
assert correction['correction_of_run_id'] == run_id
|
|
selected = ok(s.set_settlement_draft_item(correction['id'], 'account', aid, correction['version'], 'synthetic selector', valuation_id=corrected_value, db_path=path))['draft']
|
|
corrected = finish(path, selected)
|
|
assert corrected['correction']['correction_of_run_id'] == run_id
|
|
assert corrected['correction']['correction_reason'] == 'synthetic close correction'
|
|
for table, rows in original_rows.items():
|
|
assert counts(path)[table][:len(rows)] == rows
|
|
|
|
third_value = ok(s.correct_account_valuation(corrected_value, 150, 'second synthetic correction', idempotency_key='v3', db_path=path))['valuation']['id']
|
|
later = ok(s.create_settlement_draft('2026-09', 'synthetic', 'correction-2', correction_of_run_id=corrected['snapshot_run_id'], correction_reason='second synthetic close correction', db_path=path))['draft']
|
|
later = ok(s.set_settlement_draft_item(later['id'], 'account', aid, later['version'], 'synthetic selector', valuation_id=third_value, db_path=path))['draft']
|
|
finish(path, later)
|
|
before_replay = counts(path)
|
|
replay = s.finalize_settlement_draft(correction['id'], selected['version'], corrected['review_fingerprint'], 'close-' + correction['id'], db_path=path)
|
|
assert replay == corrected
|
|
assert counts(path) == before_replay
|