You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
5.7 KiB
Python

# 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"}
# 기본은 전부 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"] == "ollama"
assert d["overridden"] is False # 오버레이 없음 → env 기본
assert "ollama" in d["provider_options"]
# 보조 프로바이더 노출
assert "stt_provider" in d and "vision_provider" in d
def test_llm_update_persists_and_applies(client):
# ollama + 커스텀 모델로 저장 → 영속 + 런타임 반영
r = client.put("/api/settings/llm", json={"provider": "ollama", "model": "my-model"})
assert r.status_code == 200
body = r.json()
assert body["provider"] == "ollama"
assert body["model"] == "my-model"
assert body["overridden"] is True
# 다시 GET 해도 유지(영속)
again = client.get("/api/settings/llm").json()
assert again["provider"] == "ollama" and again["model"] == "my-model"
# 런타임 오버레이가 실제 provider 선택에 반영되는가(force 없이)
assert runtime_config.effective_llm().provider == "ollama"
assert get_provider().name == "ollama"
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_uses_ollama(client):
# phase-16+: 휴리스틱 옵션 제거 → 연결 테스트는 항상 Ollama 도달성으로 확인.
r = client.post("/api/settings/llm/test", json={"provider": "ollama"})
assert r.status_code == 200
d = r.json()
assert d["provider"] == "ollama"
assert isinstance(d["reachable"], bool) # 실 Ollama 가동 여부에 따라 달라짐(환경 의존)
def test_llm_provider_options_include_openai_gemini(client):
d = client.get("/api/settings/llm").json()
assert "openai" in d["provider_options"]
assert "gemini" in d["provider_options"]
assert d["api_key_set"] is False # 기본은 키 없음
def test_llm_openai_with_api_key_encrypted(client, session):
s, _ = session
r = client.put(
"/api/settings/llm",
json={
"provider": "openai",
"host": "https://api.openai.com/v1",
"model": "gpt-4o-mini",
"api_key": "sk-secret-xyz",
},
)
assert r.status_code == 200
body = r.json()
assert body["provider"] == "openai" and body["model"] == "gpt-4o-mini"
assert body["api_key_set"] is True
assert "api_key" not in body # 키 값은 절대 응답에 없음
# 유효 설정/팩토리에 반영(복호화 라운드트립)
eff = runtime_config.effective_llm()
assert eff.provider == "openai" and eff.api_key == "sk-secret-xyz"
assert get_provider().name == "openai"
# 저장은 암호화(평문 금지)
from app.models import AppSetting
row = s.get(AppSetting, "llm.api_key")
assert row is not None and row.value.startswith("enc:") and "sk-secret-xyz" not in row.value
def test_llm_gemini_provider_select(client):
r = client.put("/api/settings/llm", json={"provider": "gemini", "model": "gemini-2.5-flash"})
assert r.status_code == 200 and r.json()["provider"] == "gemini"
assert runtime_config.effective_llm().provider == "gemini"
assert get_provider().name == "gemini"
def test_llm_api_key_cleared_on_reset(client):
client.put("/api/settings/llm", json={"provider": "openai", "api_key": "sk-zzz"})
assert client.get("/api/settings/llm").json()["api_key_set"] is True
# 빈 문자열들 → 전부 기본으로 되돌리기(키 삭제 포함)
client.put(
"/api/settings/llm",
json={"provider": "", "model": "", "host": "", "timeout": 0, "api_key": ""},
)
d = client.get("/api/settings/llm").json()
assert d["api_key_set"] is False and d["provider"] == "ollama"
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