feat: dashboard UI + key API + realtime decode
Full HTML dashboard (dark theme, vanilla JS, no external deps) served at / and /dashboard with five tabs: system status (admin), file upload-to-transcribe with progress/result/downloads, job history with cancel/result modal, realtime mic demo over the existing WebSocket, and API key create/list. Backend: POST/GET /v1/keys (admin; raw key returned once, digest-only storage); KeyStore.list_keys(); EngineOwner.emit_hypothesis is now a real implementation (PCM16 chunk -> WAV -> realtime-lane decode with the single GPU lock) instead of a stub. Notebook: tunnel cell links /dashboard and smoke-checks the HTML. + 6 tests (dashboard public HTML, key create/list/scope, WAV header, emit_hypothesis cleanup). 153 tests pass, ruff clean, JS syntax verified with node --check.
This commit is contained in:
@@ -44,6 +44,44 @@ class TestHealth:
|
||||
assert body["model_ready"] is False # 모델 캐시 없음 → 프로비저닝 보류
|
||||
|
||||
|
||||
class TestDashboard:
|
||||
def test_dashboard_html_public(self, client: TestClient):
|
||||
"""대시보드 HTML은 공개 — 인증은 클라이언트에서 API 키 입력."""
|
||||
for path in ("/", "/dashboard"):
|
||||
r = client.get(path)
|
||||
assert r.status_code == 200
|
||||
assert "text/html" in r.headers["content-type"]
|
||||
assert "luke_scribe" in r.text
|
||||
assert "실시간" in r.text # 전체 기능 포함
|
||||
|
||||
|
||||
class TestKeys:
|
||||
def test_create_and_list_key(self, client: TestClient):
|
||||
headers = {"X-API-Key": "key-admin"}
|
||||
r = client.post("/v1/keys", json={"scopes": ["transcribe"]}, headers=headers)
|
||||
assert r.status_code == 201, r.text
|
||||
body = r.json()
|
||||
assert body["key"].startswith("luke-")
|
||||
assert body["key_id"].startswith("k-")
|
||||
# raw 키는 1회만 노출 — 목록에는 다이제스트 ID만
|
||||
r2 = client.get("/v1/keys", headers=headers)
|
||||
keys = r2.json()["keys"]
|
||||
assert body["key_id"] in [k["id"] for k in keys]
|
||||
assert all("key" not in k for k in keys)
|
||||
|
||||
def test_admin_scope_required(self, client: TestClient):
|
||||
headers = {"X-API-Key": "key-transcribe"}
|
||||
assert (
|
||||
client.post("/v1/keys", json={"scopes": ["transcribe"]}, headers=headers).status_code
|
||||
== 403
|
||||
)
|
||||
assert client.get("/v1/keys", headers=headers).status_code == 403
|
||||
|
||||
def test_no_auth_401(self, client: TestClient):
|
||||
assert client.get("/v1/keys").status_code == 401
|
||||
assert client.post("/v1/keys", json={"scopes": []}).status_code == 401
|
||||
|
||||
|
||||
class TestAutoWorker:
|
||||
def test_auto_worker_off_by_default(self, client: TestClient):
|
||||
"""기본(False)에서는 워커 스레드를 띄우지 않는다 (테스트/프로덕션 안전)."""
|
||||
|
||||
Reference in New Issue
Block a user