# backend/tests/test_settings.py — 설정 라우터(시스템 스냅샷 + LLM 편집/적용/테스트) + 프로필/토큰 from app import runtime_config from app.llm.provider import get_provider def test_system_snapshot_readonly(client): d = client.get("/api/settings/system").json() assert d["env"] == "dev" assert d["auth_enabled"] is False # 데모 기본 off assert d["database"] == "sqlite" assert set(d["connector_modes"]) == { "calendar", "mail", "chat", "finance", "health", "knowledge", } # 기본은 전부 mock assert all(v == "mock" for v in d["connector_modes"].values()) def test_llm_config_defaults_match_env(client): d = client.get("/api/settings/llm").json() assert d["provider"] in ("auto", "ollama", "heuristic") assert d["overridden"] is False # 오버레이 없음 → env 기본 assert "auto" in d["provider_options"] # 보조 프로바이더 노출 assert "embed_provider" in d and "stt_provider" in d def test_llm_update_persists_and_applies(client): # heuristic 으로 저장 → 영속 + 런타임 반영 r = client.put("/api/settings/llm", json={"provider": "heuristic", "model": "my-model"}) assert r.status_code == 200 body = r.json() assert body["provider"] == "heuristic" assert body["model"] == "my-model" assert body["overridden"] is True # 다시 GET 해도 유지(영속) again = client.get("/api/settings/llm").json() assert again["provider"] == "heuristic" and again["model"] == "my-model" # 런타임 오버레이가 실제 provider 선택에 반영되는가(force 없이) assert runtime_config.effective_llm().provider == "heuristic" assert get_provider().name == "heuristic" def test_llm_update_reset_to_default(client): client.put("/api/settings/llm", json={"provider": "ollama"}) assert client.get("/api/settings/llm").json()["provider"] == "ollama" # 빈 문자열 → 기본으로 되돌리기 r = client.put("/api/settings/llm", json={"provider": ""}) assert r.status_code == 200 assert r.json()["overridden"] is False assert runtime_config.snapshot() == {} def test_llm_update_rejects_bad_provider(client): r = client.put("/api/settings/llm", json={"provider": "gpt5"}) assert r.status_code == 400 def test_llm_test_heuristic_reachable(client): r = client.post("/api/settings/llm/test", json={"provider": "heuristic"}) assert r.status_code == 200 d = r.json() assert d["reachable"] is True and d["provider"] == "heuristic" def test_profile_update(client): r = client.patch("/api/me", json={"name": "지우님", "role": "프로덕트 리드"}) assert r.status_code == 200 d = r.json() assert d["name"] == "지우님" assert d["initial"] == "지" assert d["role"] == "프로덕트 리드" # 영속 확인 assert client.get("/api/me").json()["role"] == "프로덕트 리드" def test_api_tokens_create_list_revoke(client): # 처음엔 토큰 없음 assert client.get("/api/me/tokens").json() == [] # 생성 → 평문 1회 노출 created = client.post("/api/me/tokens", params={"name": "CLI"}).json() assert created["token"] and created["name"] == "CLI" tid = created["id"] # 목록(평문 미포함) rows = client.get("/api/me/tokens").json() assert len(rows) == 1 and rows[0]["id"] == tid and "token" not in rows[0] assert rows[0]["revoked"] is False # 폐기 assert client.delete(f"/api/me/tokens/{tid}").status_code == 200 assert client.get("/api/me/tokens").json()[0]["revoked"] is True