"""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)) monkeypatch.setattr('dashboard.authentication.get_authenticated_actor', lambda: 'fixture-operator') appfile = tmp_path / 'panel.py' appfile.write_text(f'from dashboard.page_views.settlement_drafts import render_settlement_drafts\nrender_settlement_drafts(db_path={str(path)!r})\n') app = AppTest.from_file(str(appfile)) app.session_state["authenticated_actor"] = "fixture-operator" app = app.run(timeout=20) assert not app.exception control(app.button, '정산 시작').click().run(timeout=20) control(app.button, '초안 만들기').click().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