"""Operator feedback distinguishes read failures and partially committed saves.""" import pytest from streamlit.testing.v1 import AppTest from jarca_ledger_mcp import service as s from jarca_ledger_mcp.adapters.streamlit import settlement_gateway from jarca_ledger_mcp.db import init_db def panel(monkeypatch, path): monkeypatch.setenv('JARCA_LEDGER_DB_PATH', str(path)) monkeypatch.setattr('dashboard.authentication.get_authenticated_actor', lambda: 'fixture-operator') app = AppTest.from_string(f'from dashboard.page_views.settlement_drafts import render_settlement_drafts\nrender_settlement_drafts(db_path={str(path)!r})\n') 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 control(elements, label): return next(element for element in elements if element.label == label) def test_panel_shows_safe_error_when_draft_listing_fails(tmp_path, monkeypatch): path = tmp_path / 'missing.sqlite3' monkeypatch.setattr(settlement_gateway, 'list_settlement_drafts', lambda *args, **kwargs: { 'success': False, 'error_code': 'storage_error', 'message': 'SYNTHETIC_PRIVATE_DIAGNOSTIC', }) app = panel(monkeypatch, path) assert app.error and 'storage_error' in ' '.join(element.value for element in app.error) assert not any('초안이 없습니다' in element.value for element in app.info) visible = ' '.join(element.value for elements in (app.error, app.info, app.caption) for element in elements) assert 'SYNTHETIC_PRIVATE_DIAGNOSTIC' not in visible assert not path.exists() def test_panel_reports_partial_save_when_item_update_fails(tmp_path, monkeypatch): path = str(tmp_path / 'partial.sqlite3') init_db(path) account = s.create_ledger_account('Synthetic cash', db_path=path)['account']['id'] assert s.register_account_valuation(account, '2026-09-30', 10, verification_status='verified', idempotency_key='synthetic-value', db_path=path)['success'] app = panel(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] review_key = f"review_result_{draft['id']}" assert app.session_state[review_key]['success'] control(app.text_input, '초안 메모 저장').set_value('Synthetic persisted memo') control(app.text_input, '대상 ID').set_value('synthetic-nonexistent-target') control(app.button, '저장 / 지정 / 포함·제외').click().run(timeout=20) assert not app.exception current = s.get_settlement_draft(draft['id'], db_path=path) assert current['draft']['memo'] == 'Synthetic persisted memo' assert current['draft']['version'] == draft['version'] + 1 assert len(current['items']) == 1 and current['items'][0]['source_id'] == account assert app.error assert any('메모는 저장되었지만 항목 변경은 실패했습니다' in element.value for element in app.warning) with pytest.raises(KeyError): app.session_state[review_key] assert not any(element.label in {'검토 결과 승인', '최종 마감'} for element in app.button)