from pathlib import Path import sys from streamlit.testing.v1 import AppTest sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from dashboard.app import get_transfer_review_items from dashboard import authentication from scripts.ci_fixture_db import create_ci_fixture_db def test_get_transfer_review_items_accepts_transactions_key(): payload = { "success": True, "transactions": [ {"id": "tx_1", "amount": 10000}, ], } assert get_transfer_review_items(payload) == payload["transactions"] def test_get_transfer_review_items_prefers_review_items_when_present(): payload = { "success": True, "review_items": [ {"id": "tx_legacy", "amount": 20000}, ], "transactions": [ {"id": "tx_new", "amount": 30000}, ], } assert get_transfer_review_items(payload) == payload["review_items"] def test_get_transfer_review_items_returns_empty_for_unsuccessful_payload(): assert get_transfer_review_items({"success": False, "transactions": [{"id": "tx_1"}]}) == [] assert get_transfer_review_items(None) == [] def test_streamlit_apptest_core_pages_use_synthetic_fixture_db(tmp_path, monkeypatch): fixture = tmp_path / "fixture.sqlite3" create_ci_fixture_db(fixture) monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(fixture)) monkeypatch.setenv(authentication.ADMIN_ID_ENV, "test-admin") monkeypatch.setenv(authentication.ADMIN_PASSWORD_ENV, "test-password") monkeypatch.setenv(authentication.SIGNING_SECRET_ENV, "s" * 64) settings = authentication.load_auth_settings() assert settings is not None token = authentication.issue_persistent_token(settings, remember_me=False).value monkeypatch.setattr(authentication, "_request_cookie", lambda: token) app_path = Path(__file__).resolve().parents[1] / "dashboard" / "app.py" page_expectations = { "요약": "이번 달 수입/지출 현황", "자산/마감": "월말 스냅샷 Preview / Close", "분류 규칙": "현재 분류 규칙", "확인필요 큐": "현재 review_status: pending", "월정산": "월간 핵심 KPI", } for page, expected_text in page_expectations.items(): at = AppTest.from_file(str(app_path), default_timeout=30) at.query_params["page"] = page if page == "월정산": at.query_params["month"] = "2026-06" at.run() assert not at.exception body_text = "\n".join(str(item.value) for item in list(at.text) + list(at.markdown) + list(at.caption)) assert expected_text in body_text if page == "월정산": assert "월간 핵심 KPI" in body_text assert "현금흐름 / 지출 구조" in body_text assert "예산 대비 점검" in body_text assert "정산 전 운영 블로커 점검" in body_text assert "정산 전 검토" in body_text assert "거래 반영 / 계좌 상태" in body_text assert "자산 / 순자산 마감 상태" in body_text assert "미해결 블로커" in body_text assert "미분류" in body_text assert "미분류 페이지" in body_text assert "대표 계좌 반영 상태" in body_text assert "계좌명" in body_text assert "CI Synthetic Checking" in body_text assert "기관" in body_text assert "유형" in body_text assert "최근 동기화: 2026-06-30T23:59:00+09:00" in body_text assert "잔액: 1,250,000원" in body_text assert "상태: 정상" in body_text assert "자산 상세: 경고 3건 / 제외 1건" in body_text assert "월정산 결과 / Evidence" in body_text assert "읽기 전용 근거 요약" in body_text assert "이번 범위에는 월정산 close 저장/실행이 포함되지 않습니다" in body_text assert "다음 액션" in body_text assert "예산 조정 후보" in body_text assert "분류/검토 규칙 후보" in body_text assert "대조/Import/백업 후속 작업" in body_text assert "close 준비 상태" in body_text assert "source coverage" in body_text assert "최근 Import" in body_text assert "자산/마감" in body_text metrics = {metric.label: metric.value for metric in at.metric} assert metrics["실제 수입"] == "2,000,000원" assert metrics["실제 지출"] == "35,000원" assert metrics["순현금흐름"] == "1,965,000원" assert metrics["저축률"] == "98.2%" assert metrics["예산 항목 수"] == "0건" assert metrics["월 예산"] == "50,000원" assert metrics["예산 초과 항목"] == "0건" assert metrics["미처리 review"] == "0건" assert metrics["미대조 계좌"] == "0건" assert metrics["미분류 추천 대기"] == "2건" assert metrics["자산 합계"] == "6,250,000원" assert metrics["총부채"] == "2,000,000원" assert "자산 마감 상태" in metrics assert metrics["순자산"] == "4,250,000원" assert metrics["자산 마감 상태"] == "미마감" assert metrics["최근 Import 시각"] == "없음" assert "선택 월: 2026-06" in body_text assert "기준일: 2026-06-30" in body_text assert "기준 시각" in body_text assert "모드: 읽기 전용" in body_text assert "확인필요 큐로 이동" in body_text assert "계좌 화면에서 잔액대조 확인" in body_text assert "Import 화면에서 이력 확인" in body_text assert "자산/마감 화면에서 스냅샷 확인" in body_text assert "fixture.sqlite3" not in body_text assert ".local/share/jarca/uploads" not in body_text assert "/home/" not in body_text assert "raw_payload" not in body_text assert "🔒 읽기 전용" in body_text def _dashboard_body(at): return "\\n".join(str(item.value) for item in list(at.text) + list(at.markdown) + list(at.caption)) def test_authenticated_asset_and_monthly_pages_render_evidence_details(tmp_path, monkeypatch): fixture = tmp_path / "fixture.sqlite3" create_ci_fixture_db(fixture) monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(fixture)) monkeypatch.setenv(authentication.ADMIN_ID_ENV, "test-admin") monkeypatch.setenv(authentication.ADMIN_PASSWORD_ENV, "test-password") monkeypatch.setenv(authentication.SIGNING_SECRET_ENV, "s" * 64) settings = authentication.load_auth_settings() token = authentication.issue_persistent_token(settings, remember_me=False).value monkeypatch.setattr(authentication, "_request_cookie", lambda: token) app_path = Path(__file__).resolve().parents[1] / "dashboard" / "app.py" for page in ("자산/마감", "월정산"): at = AppTest.from_file(str(app_path), default_timeout=30) at.query_params["page"] = page if page == "월정산": at.query_params["month"] = "2026-06" at.run() assert not at.exception body = _dashboard_body(at) if page == "월정산": assert "missing_verified_exact_valuation" in body assert "acct_" in body assert "제외 상세" in body assert "source_note" not in body assert "memo" not in body def test_authenticated_dashboard_all_pages_are_no_write(tmp_path, monkeypatch): fixture = tmp_path / "fixture.sqlite3" create_ci_fixture_db(fixture) monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(fixture)) monkeypatch.setenv(authentication.ADMIN_ID_ENV, "test-admin") monkeypatch.setenv(authentication.ADMIN_PASSWORD_ENV, "test-password") monkeypatch.setenv(authentication.SIGNING_SECRET_ENV, "s" * 64) settings = authentication.load_auth_settings() token = authentication.issue_persistent_token(settings, remember_me=False).value monkeypatch.setattr(authentication, "_request_cookie", lambda: token) app_path = Path(__file__).resolve().parents[1] / "dashboard" / "app.py" pages = ["월정산", "요약", "자산/마감", "거래내역", "확인필요 큐", "미분류", "분류 규칙", "카테고리", "계좌", "Import"] import sqlite3 def data_version(): with sqlite3.connect(fixture) as conn: return conn.execute("PRAGMA data_version").fetchone()[0] before_data_version = data_version() for page in pages: at = AppTest.from_file(str(app_path), default_timeout=30) at.query_params["page"] = page if page == "월정산": at.query_params["month"] = "2026-06" at.run() assert not at.exception, (page, at.exception) assert data_version() == before_data_version def test_authenticated_dashboard_missing_db_is_stable_and_does_not_initialize(tmp_path, monkeypatch): db_path = tmp_path / "missing" / "nested" / "ledger.sqlite3" monkeypatch.setenv("JARCA_LEDGER_DB_PATH", str(db_path)) monkeypatch.setenv(authentication.ADMIN_ID_ENV, "test-admin") monkeypatch.setenv(authentication.ADMIN_PASSWORD_ENV, "test-password") monkeypatch.setenv(authentication.SIGNING_SECRET_ENV, "s" * 64) settings = authentication.load_auth_settings() token = authentication.issue_persistent_token(settings, remember_me=False).value monkeypatch.setattr(authentication, "_request_cookie", lambda: token) app_path = Path(__file__).resolve().parents[1] / "dashboard" / "app.py" for page in ["월정산", "요약", "자산/마감", "거래내역", "확인필요 큐", "미분류", "분류 규칙", "카테고리", "계좌", "Import"]: at = AppTest.from_file(str(app_path), default_timeout=30) at.query_params["page"] = page at.run() assert not at.exception, (page, at.exception) body = _dashboard_body(at) + "\n".join(str(item.value) for item in at.error) assert "Ledger DB를 사용할 수 없습니다" in body assert not db_path.exists() assert not db_path.parent.exists()