from __future__ import annotations import json from pathlib import Path import sys from threading import Thread import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "src")) APP_DIR = ROOT / "web" / "operator-gui" from rights_filter.server.http_app import build_server from rights_filter.server.image_store import LocalSubmissionImageStore from rights_filter.server.sqlite_store import CopyrighterStore def _start(server): thread = Thread(target=server.serve_forever, daemon=True) thread.start() return thread def _browser_or_skip(playwright): try: return playwright.chromium.launch(headless=True) except Exception as exc: pytest.skip(f"Playwright Chromium is not available: {exc}") def _bootstrap_payload(): return { "submissions": [ { "id": "SUB-SMOKE1", "title": "Smoke sample", "asset": "/assets/case-portrait.svg", "riskScore": 42, "riskBand": "medium", "submittedAt": "2026-06-03 10:00", "submittedEpoch": 1780452000, "lastAnalysis": "2026-06-03 10:01", "applicantStatus": "검토 중", "decisionStatus": "unreviewed", "reasons": ["Naver search returned no results"], "providerState": {"internal": "ok", "naver": "empty", "google": "disabled", "llm": "pending"}, "fileFacts": {"size": "320 x 240", "format": "SVG", "submitted": "2026-06-03 10:00", "analysis": "v1"}, "derivativeNote": "브라우저 smoke test submission", "recommendation": {"label": "운영자 검토 필요", "detail": "검색 근거가 부족합니다."}, "derivedPreview": {"automatic": False, "entryName": "Smoke sample", "effect": "보강 검색 필요"}, "queryHistory": [ { "provider": "naver", "query": "Smoke sample official", "status": "empty", "timestamp": "2026-06-03 10:01", "count": 0, } ], "similar": [{"asset": "/assets/case-portrait.svg", "label": "local submission"}], "evidence": [ { "id": "ev-smoke-empty", "group": "naver", "source": "naver", "title": "Naver search returned no results", "confidence": 0, "query": "Smoke sample official", "domain": "naver", "url": "", "imageUrl": "", "thumbnailUrl": "", "pageTitle": "", "matchType": "empty", "rank": "", "providerScore": 0, "retrievedAt": "2026-06-03 10:01", "contributed": False, "sourceEvidenceIds": [], "status": "active", } ], } ], "submissionQueue": {"label": "smoke", "folderPath": "smoke", "isActive": True}, "providers": [{"id": "naver", "name": "Naver", "enabled": True, "quota": 100, "usage": 0, "status": "ok"}], "knowledgeEntries": [], "collectionCandidates": [], "corrections": [], "auditEvents": [], "coverageThresholds": {"coverageGoodRate": 70, "coverageWarnRate": 40, "queryGoodRate": 70, "queryWarnRate": 40}, "searchCoverage": { "submissions": {"total": 1, "coverageSubmissions": 0}, "queries": {"failed": 0}, "providers": [{"id": "naver", "name": "Naver", "queryEntries": 1, "evidenceSubmissions": 0}], }, } def test_browser_smoke_suggested_query_fills_manual_query_without_running_search(tmp_path: Path): playwright = pytest.importorskip("playwright.sync_api") image_root = tmp_path / "submissions" image_root.mkdir() store = CopyrighterStore(tmp_path / "copyrighter.sqlite3") store.initialize() server = build_server( host="127.0.0.1", port=0, store=store, image_store=LocalSubmissionImageStore(image_root), static_dir=APP_DIR, ) _start(server) base = f"http://127.0.0.1:{server.server_port}" try: with playwright.sync_playwright() as pw: browser = _browser_or_skip(pw) page = browser.new_page(viewport={"width": 1280, "height": 900}) browser_errors = [] page.on("console", lambda message: browser_errors.append(f"console:{message.type}:{message.text}")) page.on("pageerror", lambda error: browser_errors.append(f"pageerror:{error}")) page.route( "**/api/bootstrap", lambda route: route.fulfill( status=200, content_type="application/json", body=json.dumps(_bootstrap_payload(), ensure_ascii=False), ), ) page.goto(base + "/") try: page.wait_for_selector('#queue-body [data-select-case="SUB-SMOKE1"]', timeout=5000) except Exception as exc: pytest.fail(f"{exc}\nerrors={browser_errors}\nbody={page.locator('body').inner_text()[:1000]}") page.get_by_role("button", name="SUB-SMOKE1").click() page.get_by_role("button", name="Smoke sample 저작권").click() assert page.locator('[data-workbench-panel="queries"]').is_visible() assert page.locator("#manual-query").input_value() == "Smoke sample 저작권" assert page.locator("#manual-query-status").inner_text() == "추천 쿼리를 입력했습니다. 실행 버튼을 눌러 검색하세요.", browser_errors browser.close() finally: server.shutdown() def test_browser_uploads_image_and_selects_new_submission(tmp_path: Path): playwright = pytest.importorskip("playwright.sync_api") image_root = tmp_path / "submissions" image_root.mkdir() upload_file = tmp_path / "smoke upload.svg" upload_file.write_text("", encoding="utf-8") store = CopyrighterStore(tmp_path / "copyrighter.sqlite3") store.initialize() server = build_server( host="127.0.0.1", port=0, store=store, image_store=LocalSubmissionImageStore(image_root), static_dir=APP_DIR, ) _start(server) base = f"http://127.0.0.1:{server.server_port}" try: with playwright.sync_playwright() as pw: browser = _browser_or_skip(pw) page = browser.new_page(viewport={"width": 1280, "height": 900}) browser_errors = [] page.on("console", lambda message: browser_errors.append(f"console:{message.type}:{message.text}")) page.on("pageerror", lambda error: browser_errors.append(f"pageerror:{error}")) page.goto(base + "/") page.set_input_files("#submission-image", str(upload_file)) assert page.locator("#submission-image-name").inner_text() == "smoke upload.svg" page.get_by_role("button", name="사진 넣기").click() page.wait_for_selector('#queue-body [data-select-case="smoke-upload"]', state="attached", timeout=10000) page.wait_for_selector("#workbench-view", state="visible", timeout=10000) assert (image_root / "images" / "smoke-upload.svg").exists() assert "smoke-upload 사진이 추가되었습니다. 새 심사 건으로 바로 선택했습니다." in page.locator("#submission-import-status").inner_text() assert page.locator("#case-title").inner_text() == "smoke-upload · smoke-upload" assert not browser_errors browser.close() finally: server.shutdown()