"""Tests for LLM Model API endpoints""" import pytest from unittest.mock import patch, MagicMock class TestLLMModelAPI: """Test cases for LLM Model endpoints""" def test_get_llm_models_empty(self, client): """Test getting LLM models when database is empty""" response = client.get("/api/llm") assert response.status_code == 200 data = response.json() assert "total" in data assert "list" in data assert data["total"] == 0 def test_create_llm_model(self, client, sample_llm_model_data): """Test creating a new LLM model""" response = client.post("/api/llm", json=sample_llm_model_data) assert response.status_code == 200 data = response.json() assert data["name"] == sample_llm_model_data["name"] assert data["vendor"] == sample_llm_model_data["vendor"] assert data["type"] == sample_llm_model_data["type"] assert data["base_url"] == sample_llm_model_data["base_url"] assert "id" in data def test_create_llm_model_minimal(self, client): """Test creating an LLM model with minimal required data""" data = { "name": "Minimal LLM", "vendor": "Test", "type": "text", "base_url": "https://api.test.com", "api_key": "test-key" } response = client.post("/api/llm", json=data) assert response.status_code == 200 assert response.json()["name"] == "Minimal LLM" def test_get_llm_model_by_id(self, client, sample_llm_model_data): """Test getting a specific LLM model by ID""" # Create first create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] # Get by ID response = client.get(f"/api/llm/{model_id}") assert response.status_code == 200 data = response.json() assert data["id"] == model_id assert data["name"] == sample_llm_model_data["name"] def test_get_llm_model_not_found(self, client): """Test getting a non-existent LLM model""" response = client.get("/api/llm/non-existent-id") assert response.status_code == 404 def test_update_llm_model(self, client, sample_llm_model_data): """Test updating an LLM model""" # Create first create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] # Update update_data = { "name": "Updated LLM Model", "vendor": "SiliconFlow", "type": "embedding", "temperature": 0.5, "context_length": 8192 } response = client.put(f"/api/llm/{model_id}", json=update_data) assert response.status_code == 200 data = response.json() assert data["name"] == "Updated LLM Model" assert data["vendor"] == "SiliconFlow" assert data["type"] == "embedding" assert data["temperature"] == 0.5 assert data["context_length"] == 8192 def test_delete_llm_model(self, client, sample_llm_model_data): """Test deleting an LLM model""" # Create first create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] # Delete response = client.delete(f"/api/llm/{model_id}") assert response.status_code == 200 # Verify deleted get_response = client.get(f"/api/llm/{model_id}") assert get_response.status_code == 404 def test_list_llm_models_with_pagination(self, client, sample_llm_model_data): """Test listing LLM models with pagination""" # Create multiple models for i in range(3): data = sample_llm_model_data.copy() data["id"] = f"test-llm-{i}" data["name"] = f"LLM Model {i}" client.post("/api/llm", json=data) # Test pagination response = client.get("/api/llm?page=1&limit=2") assert response.status_code == 200 data = response.json() assert data["total"] == 3 assert len(data["list"]) == 2 def test_filter_llm_models_by_type(self, client, sample_llm_model_data): """Test filtering LLM models by type""" # Create models with different types for i, model_type in enumerate(["text", "embedding", "rerank"]): data = sample_llm_model_data.copy() data["id"] = f"test-llm-{model_type}" data["name"] = f"LLM {model_type}" data["type"] = model_type client.post("/api/llm", json=data) # Filter by type response = client.get("/api/llm?model_type=text") assert response.status_code == 200 data = response.json() assert data["total"] >= 1 for model in data["list"]: assert model["type"] == "text" def test_filter_llm_models_by_enabled(self, client, sample_llm_model_data): """Test filtering LLM models by enabled status""" # Create enabled and disabled models data = sample_llm_model_data.copy() data["id"] = "test-llm-enabled" data["name"] = "Enabled LLM" data["enabled"] = True client.post("/api/llm", json=data) data["id"] = "test-llm-disabled" data["name"] = "Disabled LLM" data["enabled"] = False client.post("/api/llm", json=data) # Filter by enabled response = client.get("/api/llm?enabled=true") assert response.status_code == 200 data = response.json() for model in data["list"]: assert model["enabled"] == True def test_create_llm_model_with_all_fields(self, client): """Test creating an LLM model with all fields""" data = { "id": "full-llm", "name": "Full LLM Model", "vendor": "OpenAI", "type": "text", "base_url": "https://api.openai.com/v1", "api_key": "sk-test", "model_name": "gpt-4", "temperature": 0.8, "context_length": 16384, "enabled": True } response = client.post("/api/llm", json=data) assert response.status_code == 200 result = response.json() assert result["name"] == "Full LLM Model" assert result["temperature"] == 0.8 assert result["context_length"] == 16384 @patch('httpx.Client') def test_test_llm_model_success(self, mock_client_class, client, sample_llm_model_data): """Test testing an LLM model with successful connection""" # Create model first create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] # Mock the HTTP response mock_client = MagicMock() mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "choices": [{"message": {"content": "OK"}}] } mock_response.raise_for_status = MagicMock() mock_client.post.return_value = mock_response mock_client.__enter__ = MagicMock(return_value=mock_client) mock_client.__exit__ = MagicMock(return_value=False) with patch('app.routers.llm.httpx.Client', return_value=mock_client): response = client.post(f"/api/llm/{model_id}/test") assert response.status_code == 200 data = response.json() assert data["success"] == True @patch('httpx.Client') def test_test_llm_model_failure(self, mock_client_class, client, sample_llm_model_data): """Test testing an LLM model with failed connection""" # Create model first create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] # Mock HTTP error mock_client = MagicMock() mock_response = MagicMock() mock_response.status_code = 401 mock_response.text = "Unauthorized" mock_response.raise_for_status = MagicMock(side_effect=Exception("401 Unauthorized")) mock_client.post.return_value = mock_response mock_client.__enter__ = MagicMock(return_value=mock_client) mock_client.__exit__ = MagicMock(return_value=False) with patch('app.routers.llm.httpx.Client', return_value=mock_client): response = client.post(f"/api/llm/{model_id}/test") assert response.status_code == 200 data = response.json() assert data["success"] == False def test_different_llm_vendors(self, client): """Test creating LLM models with different vendors""" vendors = ["OpenAI", "SiliconFlow", "ZhipuAI", "Anthropic"] for vendor in vendors: data = { "id": f"test-{vendor.lower()}", "name": f"Test {vendor}", "vendor": vendor, "type": "text", "base_url": f"https://api.{vendor.lower()}.com/v1", "api_key": "test-key" } response = client.post("/api/llm", json=data) assert response.status_code == 200 assert response.json()["vendor"] == vendor def test_embedding_llm_model(self, client): """Test creating an embedding LLM model""" data = { "id": "embedding-test", "name": "Embedding Model", "vendor": "OpenAI", "type": "embedding", "base_url": "https://api.openai.com/v1", "api_key": "test-key", "model_name": "text-embedding-3-small" } response = client.post("/api/llm", json=data) assert response.status_code == 200 assert response.json()["type"] == "embedding" def test_preview_llm_model_success(self, client, sample_llm_model_data, monkeypatch): """Test LLM preview endpoint returns model reply.""" from app.routers import llm as llm_router create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] class DummyResponse: status_code = 200 def json(self): return { "choices": [{"message": {"content": "Preview OK"}}], "usage": {"prompt_tokens": 10, "completion_tokens": 2, "total_tokens": 12} } @property def text(self): return '{"ok":true}' class DummyClient: def __init__(self, *args, **kwargs): pass def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def post(self, url, json=None, headers=None): assert url.endswith("/chat/completions") assert headers["Authorization"] == f"Bearer {sample_llm_model_data['api_key']}" assert json["messages"][0]["role"] == "user" return DummyResponse() monkeypatch.setattr(llm_router.httpx, "Client", DummyClient) response = client.post(f"/api/llm/{model_id}/preview", json={"message": "hello"}) assert response.status_code == 200 data = response.json() assert data["success"] is True assert data["reply"] == "Preview OK" def test_preview_llm_model_reject_empty_message(self, client, sample_llm_model_data): """Test LLM preview endpoint validates message.""" create_response = client.post("/api/llm", json=sample_llm_model_data) model_id = create_response.json()["id"] response = client.post(f"/api/llm/{model_id}/preview", json={"message": " "}) assert response.status_code == 400