Files
jarca-ledger-mcp/tests/test_settlement_draft_panel_workflow.py
T

155 lines
9.4 KiB
Python

"""Real AppTest controls use synthetic SQLite, not mocked gateway results.
These are lower-level panel tests; existing dashboard authentication suites own
entrypoint authentication acceptance.
"""
import pytest
from streamlit.testing.v1 import AppTest
from jarca_ledger_mcp import service as s
from jarca_ledger_mcp.db import init_db
def control(elements, label):
return next(e for e in elements if e.label == label)
def app_for(tmp_path, monkeypatch, path):
monkeypatch.setenv('JARCA_LEDGER_DB_PATH', str(path))
appfile = tmp_path / 'panel.py'
appfile.write_text('from dashboard.page_views.settlement_drafts import render_settlement_drafts\nrender_settlement_drafts()\n')
app = AppTest.from_file(str(appfile)).run(timeout=20)
assert not app.exception
return app
def test_panel_selects_after_date_evidence_approves_and_finalizes_actual_database(tmp_path, monkeypatch):
path = str(tmp_path / 'panel.sqlite3')
init_db(path)
account = s.create_ledger_account('Synthetic retirement', type='investment', liquidity_bucket='investment', db_path=path)['account']['id']
value = s.register_account_valuation(account, '2026-10-05', 100, valuation_kind='total_value', verification_status='verified', idempotency_key='future', db_path=path)['valuation']['id']
app = app_for(tmp_path, monkeypatch, path)
control(app.button, '초안 생성 / 정정 초안').click().run(timeout=20)
control(app.text_input, '대상 ID').set_value(account)
control(app.text_input, '평가자료 ID (비우면 해제)').set_value(value)
control(app.selectbox, '선택 방식').select('manual_substitute')
control(app.text_input, '선택 사유').set_value('Synthetic late statement')
control(app.button, '저장 / 지정 / 포함·제외').click().run(timeout=20)
assert not app.exception and not app.error
control(app.button, '검토 / 새로고침 대상').click().run(timeout=20)
assert any('대체 평가자료를 포함한 월정산' in w.value for w in app.warning)
control(app.text_input, '승인 사유').set_value('Synthetic reviewer accepted date')
control(app.text_input, '승인 근거').set_value('Synthetic reference')
control(app.checkbox, '대체자료 승인').check()
control(app.button, '검토 결과 승인').click().run(timeout=20)
assert not app.exception and not app.error
control(app.button, '최종 마감').click().run(timeout=20)
assert not app.exception and not app.error
# AppTest 1.58 retains pre-rerun controls in the click-run delta tree,
# including in a flat synthetic app. Check immediate history here;
# control absence below uses a fresh instance, not a claim about live DOM.
assert any('불변 이력' in e.value for e in app.markdown)
drafts = s.list_settlement_drafts('2026-09', db_path=path)['drafts']
assert len(drafts) == 1 and drafts[0]['status'] == 'completed'
final = s.get_settlement_draft(drafts[0]['id'], db_path=path)['final_response']
assert final['totals']['net_worth'] == 100 and final['contains_substitutions']
assert final['items'][0]['selection_reason'] == 'Synthetic late statement'
# Reusing the stale tree also raises KeyError for removed selectbox state.
# Reopen the real panel against the same completed SQLite draft instead.
app = app_for(tmp_path, monkeypatch, path)
assert not app.exception and '불변 이력' in ' '.join(e.value for e in app.markdown)
assert not any(e.label == '최종 마감' for e in app.button)
assert not any(e.label == '검토 결과 승인' for e in app.button)
assert not any('현재 승인 대상 fingerprint:' in e.value for e in app.caption)
def test_panel_valuation_listing_shows_real_selected_evidence_ids(tmp_path, monkeypatch):
path = str(tmp_path / 'list.sqlite3')
init_db(path)
account = s.create_ledger_account('Synthetic cash', db_path=path)['account']['id']
vid = s.register_account_valuation(account, '2026-09-30', 10, verification_status='verified', idempotency_key='value', db_path=path)['valuation']['id']
app = app_for(tmp_path, monkeypatch, path)
control(app.button, '초안 생성 / 정정 초안').click().run(timeout=20)
control(app.text_input, '대상 ID').set_value(account)
control(app.button, '평가자료 조회').click().run(timeout=20)
assert not app.exception
assert vid in ' '.join(e.value for e in app.json)
def test_panel_supports_investment_total_and_explicit_loan_definition(tmp_path, monkeypatch):
path = str(tmp_path / 'register.sqlite3')
init_db(path)
loan = s.create_ledger_account('Synthetic loan', type='loan', liquidity_bucket='loan', db_path=path)['account']['id']
app = app_for(tmp_path, monkeypatch, path)
control(app.button, '초안 생성 / 정정 초안').click().run(timeout=20)
assert {'total_value', 'cash_balance'} <= set(control(app.selectbox, '평가 종류').options)
control(app.text_input, '평가 등록 계좌 ID').set_value(loan)
control(app.text_input, '평가일 YYYY-MM-DD').set_value('2026-09-30')
control(app.number_input, '금액').set_value(40)
control(app.selectbox, '평가 종류').select('loan_balance')
control(app.selectbox, '대표 부채 정의').select('principal_only')
control(app.selectbox, '확인 상태').select('verified')
control(app.text_input, '등록 idempotency key').set_value('synthetic-loan-value')
control(app.button, '평가자료 등록').click().run(timeout=20)
assert not app.exception and not app.error
values = s.list_account_valuations(loan, db_path=path)['valuations']
assert len(values) == 1 and values[0]['representative_liability_definition'] == 'principal_only'
assert values[0]['amount'] == 40
def test_panel_edit_invalidates_cached_review_before_approval(tmp_path, monkeypatch):
path = str(tmp_path / 'edit.sqlite3')
init_db(path)
account = s.create_ledger_account('Synthetic cash', db_path=path)['account']['id']
s.register_account_valuation(account, '2026-09-30', 10, verification_status='verified', idempotency_key='value', db_path=path)
app = app_for(tmp_path, monkeypatch, path)
control(app.button, '초안 생성 / 정정 초안').click().run(timeout=20)
control(app.button, '검토 / 새로고침 대상').click().run(timeout=20)
draft = s.list_settlement_drafts('2026-09', db_path=path)['drafts'][0]
key = f"review_result_{draft['id']}"
old = app.session_state[key]['review_fingerprint']
assert any(old in e.value and '현재 승인 대상' in e.value for e in app.caption)
control(app.text_input, '초안 메모 저장').set_value('Synthetic edited memo')
control(app.button, '저장 / 지정 / 포함·제외').click().run(timeout=20)
assert not app.exception and not app.error
assert not any(old in e.value and '현재 승인 대상' in e.value for e in app.caption)
assert not any(e.label == '검토 결과 승인' for e in app.button)
assert not any(e.label == '최종 마감' for e in app.button)
with pytest.raises(KeyError):
app.session_state[key]
current = s.get_settlement_draft(draft['id'], db_path=path)['draft']
assert current['version'] == draft['version'] + 1 and current['memo'] == 'Synthetic edited memo'
control(app.button, '검토 / 새로고침 대상').click().run(timeout=20)
control(app.text_input, '승인 사유').set_value('Synthetic fresh review')
control(app.text_input, '승인 근거').set_value('Synthetic reference')
control(app.button, '검토 결과 승인').click().run(timeout=20)
assert not app.exception and not app.error
control(app.button, '최종 마감').click().run(timeout=20)
assert not app.exception and not app.error
assert s.get_settlement_draft(draft['id'], db_path=path)['draft']['status'] == 'completed'
def test_panel_correction_invalidates_review_without_registration_account_input(tmp_path, monkeypatch):
path = str(tmp_path / 'correction.sqlite3')
init_db(path)
account = s.create_ledger_account('Synthetic cash', db_path=path)['account']['id']
vid = s.register_account_valuation(account, '2026-09-30', 10, verification_status='verified', idempotency_key='value', db_path=path)['valuation']['id']
app = app_for(tmp_path, monkeypatch, path)
control(app.button, '초안 생성 / 정정 초안').click().run(timeout=20)
control(app.button, '검토 / 새로고침 대상').click().run(timeout=20)
draft = s.list_settlement_drafts('2026-09', db_path=path)['drafts'][0]
old = app.session_state[f"review_result_{draft['id']}"]['review_fingerprint']
assert control(app.text_input, '평가 등록 계좌 ID').value == ''
control(app.text_input, '정정할 valuation ID').set_value(vid)
control(app.number_input, '정정 금액').set_value(20)
control(app.text_input, '평가 정정 사유').set_value('Synthetic correction')
control(app.text_input, '평가 정정 키').set_value('synthetic-child')
control(app.selectbox, '정정 확인 상태').select('verified')
control(app.button, '평가자료 정정').click().run(timeout=20)
assert not app.exception and not app.error
assert not any(old in e.value and '현재 승인 대상' in e.value for e in app.caption)
assert not any(e.label == '검토 결과 승인' for e in app.button)
detail = s.get_settlement_draft(draft['id'], db_path=path)
assert detail['items'][0]['review_needed'] and detail['items'][0]['valuation_id'] == vid
assert len(s.list_account_valuations(account, db_path=path)['valuations']) == 2