657 lines
23 KiB
Python
657 lines
23 KiB
Python
"""Tests for the settings store, LLM client, and settings routes.
|
|
|
|
All LLM calls are mocked — CI never touches the network.
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
import app.llm as llm_module
|
|
from app.llm import LLMResult, expand_query, is_configured
|
|
from app.llm import ExpansionResult
|
|
from app.models import AppSetting
|
|
from app.settings_store import LLMConfig, get_app_settings, save_app_settings
|
|
|
|
# Alias to avoid pytest collecting it as a test function
|
|
_test_connection = llm_module.test_connection
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# LLMConfig dataclass defaults
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestLLMConfigDefaults:
|
|
def test_default_values(self):
|
|
cfg = LLMConfig()
|
|
assert cfg.enabled is False
|
|
assert cfg.base_url == "https://api.openai.com/v1"
|
|
assert cfg.model == ""
|
|
assert cfg.api_key == ""
|
|
assert cfg.ai_search_enabled is False
|
|
assert cfg.ai_search_extra_hints == ""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# settings_store: get_app_settings
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetAppSettings:
|
|
def test_returns_defaults_when_no_rows(self, db_session):
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.enabled is False
|
|
assert cfg.base_url == "https://api.openai.com/v1"
|
|
assert cfg.model == ""
|
|
assert cfg.api_key == ""
|
|
assert cfg.ai_search_enabled is False
|
|
|
|
def test_reads_stored_values(self, db_session):
|
|
db_session.add(AppSetting(key="llm_enabled", value="true"))
|
|
db_session.add(AppSetting(key="llm_base_url", value="https://custom.api/v1"))
|
|
db_session.add(AppSetting(key="llm_model", value="gpt-4o"))
|
|
db_session.add(AppSetting(key="llm_api_key", value="sk-test-key"))
|
|
db_session.add(AppSetting(key="ai_search_enabled", value="true"))
|
|
db_session.commit()
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.enabled is True
|
|
assert cfg.base_url == "https://custom.api/v1"
|
|
assert cfg.model == "gpt-4o"
|
|
assert cfg.api_key == "sk-test-key"
|
|
assert cfg.ai_search_enabled is True
|
|
|
|
def test_handles_null_value_as_default(self, db_session):
|
|
db_session.add(AppSetting(key="llm_model", value=None))
|
|
db_session.commit()
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.model == ""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# settings_store: save_app_settings
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSaveAppSettings:
|
|
def test_saves_new_settings(self, db_session):
|
|
save_app_settings(
|
|
db_session,
|
|
enabled=True,
|
|
base_url="https://my-api.com/v1",
|
|
model="gpt-4o-mini",
|
|
api_key="sk-new-key",
|
|
)
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.enabled is True
|
|
assert cfg.base_url == "https://my-api.com/v1"
|
|
assert cfg.model == "gpt-4o-mini"
|
|
assert cfg.api_key == "sk-new-key"
|
|
|
|
def test_updates_existing_settings(self, db_session):
|
|
save_app_settings(db_session, enabled=True, model="old-model", api_key="key1")
|
|
save_app_settings(db_session, model="new-model")
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.model == "new-model"
|
|
# enabled was not passed in second save, so it stays unchanged
|
|
assert cfg.enabled is True
|
|
|
|
def test_api_key_none_preserves_old_key(self, db_session):
|
|
save_app_settings(db_session, api_key="sk-original")
|
|
save_app_settings(db_session, model="gpt-4o", api_key=None)
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.api_key == "sk-original"
|
|
assert cfg.model == "gpt-4o"
|
|
|
|
def test_api_key_empty_string_overwrites(self, db_session):
|
|
save_app_settings(db_session, api_key="sk-original")
|
|
save_app_settings(db_session, api_key="")
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.api_key == ""
|
|
|
|
def test_partial_save_only_updates_specified_fields(self, db_session):
|
|
save_app_settings(db_session, enabled=True, model="gpt-4o")
|
|
save_app_settings(db_session, base_url="https://new.url/v1")
|
|
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.enabled is True
|
|
assert cfg.model == "gpt-4o"
|
|
assert cfg.base_url == "https://new.url/v1"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# is_configured
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestIsConfigured:
|
|
def test_false_when_disabled(self):
|
|
cfg = LLMConfig(enabled=False, model="gpt-4o", api_key="sk-key")
|
|
assert is_configured(cfg) is False
|
|
|
|
def test_false_when_no_model(self):
|
|
cfg = LLMConfig(enabled=True, model="", api_key="sk-key")
|
|
assert is_configured(cfg) is False
|
|
|
|
def test_false_when_no_api_key(self):
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="")
|
|
assert is_configured(cfg) is False
|
|
|
|
def test_true_when_all_set(self):
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
assert is_configured(cfg) is True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_connection (mocked)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTestConnection:
|
|
def test_returns_failure_when_not_configured(self):
|
|
cfg = LLMConfig(enabled=False)
|
|
result = _test_connection(cfg)
|
|
assert result.success is False
|
|
assert "未配置" in result.message
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_success_when_configured(self, mock_call):
|
|
mock_call.return_value = {"choices": [{"message": {"content": "Hi"}}]}
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
result = _test_connection(cfg)
|
|
assert result.success is True
|
|
assert "连接成功" in result.message
|
|
assert "gpt-4o" in result.message
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_handles_http_error(self, mock_call):
|
|
import httpx
|
|
|
|
mock_call.side_effect = httpx.HTTPStatusError(
|
|
"401",
|
|
request=httpx.Request("POST", "http://x"),
|
|
response=httpx.Response(401),
|
|
)
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-bad")
|
|
result = _test_connection(cfg)
|
|
assert result.success is False
|
|
assert "401" in result.message
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_handles_connect_error(self, mock_call):
|
|
import httpx
|
|
|
|
mock_call.side_effect = httpx.ConnectError("refused")
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
result = _test_connection(cfg)
|
|
assert result.success is False
|
|
assert "无法连接" in result.message
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_handles_timeout(self, mock_call):
|
|
import httpx
|
|
|
|
mock_call.side_effect = httpx.TimeoutException("timeout")
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
result = _test_connection(cfg)
|
|
assert result.success is False
|
|
assert "超时" in result.message
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# expand_query (mocked)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestExpandQuery:
|
|
def test_returns_empty_when_not_configured(self):
|
|
cfg = LLMConfig(enabled=False)
|
|
result = expand_query(cfg, "锅")
|
|
assert result.terms == []
|
|
assert result.error is None
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_expands_query_successfully(self, mock_call):
|
|
mock_call.return_value = {
|
|
"choices": [
|
|
{"message": {"content": '["平底锅","炒锅","锅具","厨房锅"]'}}
|
|
]
|
|
}
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
result = expand_query(cfg, "锅")
|
|
assert "平底锅" in result.terms
|
|
assert "炒锅" in result.terms
|
|
assert result.error is None
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_fallback_on_api_failure(self, mock_call):
|
|
mock_call.side_effect = Exception("network down")
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
result = expand_query(cfg, "锅")
|
|
assert result.terms == []
|
|
assert result.error is not None
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_fallback_on_empty_response(self, mock_call):
|
|
mock_call.return_value = {"choices": [{"message": {"content": ""}}]}
|
|
cfg = LLMConfig(enabled=True, model="gpt-4o", api_key="sk-key")
|
|
result = expand_query(cfg, "锅")
|
|
assert result.terms == []
|
|
assert result.error is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Routes: GET /settings
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSettingsPage:
|
|
def test_settings_page_returns_200(self, client):
|
|
response = client.get("/settings")
|
|
assert response.status_code == 200
|
|
|
|
def test_settings_page_has_form_elements(self, client):
|
|
response = client.get("/settings")
|
|
assert "设置" in response.text
|
|
assert 'name="enabled"' in response.text
|
|
assert 'name="base_url"' in response.text
|
|
assert 'name="model"' in response.text
|
|
assert 'name="api_key"' in response.text
|
|
assert "保存设置" in response.text
|
|
assert "测试连接" in response.text
|
|
|
|
def test_settings_page_shows_nav_link(self, client):
|
|
response = client.get("/boxes")
|
|
assert "设置" in response.text
|
|
assert 'href="/settings"' in response.text
|
|
|
|
def test_settings_page_no_api_key_echoed(self, client, db_session):
|
|
save_app_settings(db_session, api_key="sk-super-secret-key-12345")
|
|
response = client.get("/settings")
|
|
assert "sk-super-secret-key-12345" not in response.text
|
|
assert "已配置" in response.text
|
|
|
|
def test_settings_page_shows_placeholder_when_no_key(self, client):
|
|
response = client.get("/settings")
|
|
assert "输入 API Key" in response.text
|
|
|
|
def test_settings_page_shows_default_base_url(self, client):
|
|
response = client.get("/settings")
|
|
assert "https://api.openai.com/v1" in response.text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Routes: POST /settings
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSaveSettingsRoute:
|
|
def test_save_settings_redirects(self, client):
|
|
response = client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://my-api.com/v1",
|
|
"model": "gpt-4o-mini",
|
|
"api_key": "sk-test-key",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert response.status_code == 303
|
|
assert response.headers["location"] == "/settings"
|
|
|
|
def test_saved_settings_persist(self, client, db_session):
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://my-api.com/v1",
|
|
"model": "gpt-4o-mini",
|
|
"api_key": "sk-test-key",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.enabled is True
|
|
assert cfg.base_url == "https://my-api.com/v1"
|
|
assert cfg.model == "gpt-4o-mini"
|
|
assert cfg.api_key == "sk-test-key"
|
|
|
|
def test_save_with_blank_api_key_preserves_old(self, client, db_session):
|
|
# First save with a key
|
|
client.post(
|
|
"/settings",
|
|
data={"enabled": "on", "model": "gpt-4o", "api_key": "sk-original"},
|
|
follow_redirects=False,
|
|
)
|
|
# Second save without key (blank)
|
|
client.post(
|
|
"/settings",
|
|
data={"enabled": "on", "model": "gpt-4o", "api_key": ""},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.api_key == "sk-original"
|
|
|
|
def test_save_disabled_state(self, client, db_session):
|
|
# First enable
|
|
client.post(
|
|
"/settings",
|
|
data={"enabled": "on", "model": "gpt-4o", "api_key": "sk-key"},
|
|
follow_redirects=False,
|
|
)
|
|
# Then disable (no 'enabled' checkbox)
|
|
client.post(
|
|
"/settings",
|
|
data={"model": "gpt-4o", "api_key": ""},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.enabled is False
|
|
|
|
def test_save_settings_no_api_key_in_redirect_page(self, client):
|
|
client.post(
|
|
"/settings",
|
|
data={"enabled": "on", "model": "gpt-4o", "api_key": "sk-secret-key"},
|
|
follow_redirects=False,
|
|
)
|
|
response = client.get("/settings")
|
|
assert "sk-secret-key" not in response.text
|
|
|
|
def test_save_refuses_when_base_url_changes_and_key_blank(self, client, db_session):
|
|
"""P1 fix: if base_url changes and api_key is blank, refuse save with error."""
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://old-api.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "sk-old-key",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
# Try saving with different base_url, no key
|
|
response = client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://new-api.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "", # blank + base_url changed → refuse
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "请重新输入 API Key 后保存" in response.text
|
|
# Old config should be unchanged — nothing was saved
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.base_url == "https://old-api.com/v1"
|
|
assert cfg.api_key == "sk-old-key"
|
|
|
|
def test_save_preserves_key_when_endpoint_unchanged_and_key_blank(self, client, db_session):
|
|
"""P1 fix: if endpoint is unchanged and api_key is blank, keep old key."""
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "sk-original",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
# Re-save same endpoint, blank key
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.api_key == "sk-original"
|
|
|
|
def test_save_preserves_key_when_only_model_changes_and_key_blank(self, client, db_session):
|
|
"""Model change alone should not clear the key — same base_url, different model."""
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "sk-original",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
# Change only model, leave api_key blank
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o-mini",
|
|
"api_key": "",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.model == "gpt-4o-mini"
|
|
assert cfg.api_key == "sk-original"
|
|
|
|
def test_save_includes_ai_search_enabled_checkbox(self, client, db_session):
|
|
"""Saving settings now also persists the ai_search_enabled checkbox."""
|
|
# Set ai_search_enabled to true first
|
|
db_session.add(AppSetting(key="ai_search_enabled", value="true"))
|
|
db_session.commit()
|
|
# Save without the checkbox → ai_search_enabled is set to False
|
|
client.post(
|
|
"/settings",
|
|
data={"enabled": "on", "model": "gpt-4o", "api_key": "sk-key"},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.ai_search_enabled is False
|
|
|
|
def test_save_preserves_ai_search_enabled_when_checked(self, client, db_session):
|
|
"""Saving settings with ai_search_enabled checked persists it."""
|
|
client.post(
|
|
"/settings",
|
|
data={
|
|
"enabled": "on",
|
|
"model": "gpt-4o",
|
|
"api_key": "sk-key",
|
|
"ai_search_enabled": "on",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
cfg = get_app_settings(db_session)
|
|
assert cfg.ai_search_enabled is True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Routes: POST /settings/test
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTestConnectionRoute:
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_test_connection_success(self, mock_call, client):
|
|
mock_call.return_value = {"choices": [{"message": {"content": "Hi"}}]}
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "sk-test",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "连接成功" in response.text
|
|
assert "gpt-4o" in response.text
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_test_connection_failure(self, mock_call, client):
|
|
import httpx
|
|
|
|
mock_call.side_effect = httpx.HTTPStatusError(
|
|
"401",
|
|
request=httpx.Request("POST", "http://x"),
|
|
response=httpx.Response(401),
|
|
)
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "sk-bad",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "连接失败" in response.text
|
|
assert "401" in response.text
|
|
|
|
def test_test_connection_not_configured(self, client):
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "", # not checked
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "",
|
|
"api_key": "",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "未配置" in response.text
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_test_connection_uses_stored_key_when_endpoint_matches(self, mock_call, client, db_session):
|
|
"""When api_key is blank but base_url and model match saved config, the stored key should be used."""
|
|
mock_call.return_value = {"choices": [{"message": {"content": "Hi"}}]}
|
|
# Store a config first
|
|
save_app_settings(
|
|
db_session,
|
|
enabled=True,
|
|
base_url="https://api.openai.com/v1",
|
|
model="gpt-4o",
|
|
api_key="sk-stored-key",
|
|
)
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o",
|
|
"api_key": "", # blank → use stored key (endpoint matches)
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "连接成功" in response.text
|
|
|
|
@patch("app.llm._call_chat_completion")
|
|
def test_test_connection_uses_stored_key_when_only_model_changes(self, mock_call, client, db_session):
|
|
"""Model changes under the same base_url can reuse the stored key."""
|
|
captured = {}
|
|
|
|
def fake_call(cfg, **kwargs):
|
|
captured["base_url"] = cfg.base_url
|
|
captured["model"] = cfg.model
|
|
captured["api_key"] = cfg.api_key
|
|
return {"choices": [{"message": {"content": "Hi"}}]}
|
|
|
|
mock_call.side_effect = fake_call
|
|
save_app_settings(
|
|
db_session,
|
|
enabled=True,
|
|
base_url="https://api.openai.com/v1",
|
|
model="gpt-4o",
|
|
api_key="sk-stored-key",
|
|
)
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o-mini",
|
|
"api_key": "",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "连接成功" in response.text
|
|
assert captured == {
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-4o-mini",
|
|
"api_key": "sk-stored-key",
|
|
}
|
|
|
|
def test_test_connection_refuses_stored_key_when_endpoint_changed(self, client, db_session):
|
|
"""When base_url changed and api_key is blank, refuse to test."""
|
|
save_app_settings(
|
|
db_session,
|
|
enabled=True,
|
|
base_url="https://api.openai.com/v1",
|
|
model="gpt-4o",
|
|
api_key="sk-stored-key",
|
|
)
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "on",
|
|
"base_url": "https://attacker.example/v1", # different endpoint
|
|
"model": "gpt-4o",
|
|
"api_key": "", # blank → refuse
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "请重新输入 API Key" in response.text
|
|
|
|
def test_test_connection_result_shows_on_settings_page(self, client):
|
|
"""Test result is rendered on the same settings page."""
|
|
response = client.post(
|
|
"/settings/test",
|
|
data={
|
|
"enabled": "",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "",
|
|
"api_key": "",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "设置" in response.text
|
|
assert "保存设置" in response.text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Graceful degradation: unconfigured LLM does not affect existing features
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGracefulDegradation:
|
|
def test_boxes_page_works_without_llm_config(self, client):
|
|
response = client.get("/boxes")
|
|
assert response.status_code == 200
|
|
|
|
def test_search_page_works_without_llm_config(self, client):
|
|
response = client.get("/search?q=test")
|
|
assert response.status_code == 200
|
|
|
|
def test_crud_works_without_llm_config(self, client, db_session):
|
|
from app.models import Box
|
|
|
|
response = client.post(
|
|
"/boxes",
|
|
data={"name": "No LLM Box"},
|
|
follow_redirects=False,
|
|
)
|
|
assert response.status_code == 303
|
|
assert db_session.query(Box).count() == 1
|