Livrare LOT 1 - Didi
This commit is contained in:
commit
5380c3fc63
990 changed files with 133308 additions and 0 deletions
209
ai_platform/modules/llm-inference/tests/test_api.py
Normal file
209
ai_platform/modules/llm-inference/tests/test_api.py
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
"""Tests for FastAPI routes."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from llm_inference.api.app import create_app
|
||||
from llm_inference.api.dependencies import init_concurrency_limiter
|
||||
from llm_inference.client import LLMClient
|
||||
from llm_inference.config import LLMSettings, SettingsCache
|
||||
from llm_inference.schemas import CompletionResponse
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_client(test_settings: LLMSettings) -> TestClient:
|
||||
"""Create a test client with mocked client."""
|
||||
# Inject test settings into cache BEFORE creating app
|
||||
SettingsCache.set(test_settings)
|
||||
|
||||
# Initialize concurrency limiter for tests
|
||||
init_concurrency_limiter(test_settings.max_concurrent_completions)
|
||||
|
||||
app = create_app()
|
||||
app.state.settings = test_settings
|
||||
app.state.client = LLMClient(settings=test_settings)
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
class TestHealthRoutes:
|
||||
"""Tests for health check routes."""
|
||||
|
||||
def test_health_endpoint(self, app_client: TestClient) -> None:
|
||||
"""Test /health endpoint."""
|
||||
response = app_client.get("/health")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "status" in data
|
||||
assert "backends" in data
|
||||
assert data["status"] in ["healthy", "degraded", "unhealthy"]
|
||||
|
||||
def test_ready_endpoint(self, app_client: TestClient) -> None:
|
||||
"""Test /ready endpoint."""
|
||||
response = app_client.get("/ready")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["ready"] is True
|
||||
|
||||
|
||||
class TestModelsRoutes:
|
||||
"""Tests for model management routes."""
|
||||
|
||||
def test_list_models(self, app_client: TestClient) -> None:
|
||||
"""Test GET /v1/models."""
|
||||
response = app_client.get("/v1/models")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
assert "object" in data
|
||||
assert data["object"] == "list"
|
||||
|
||||
def test_list_models_with_backend_filter(self, app_client: TestClient) -> None:
|
||||
"""Test GET /v1/models with backend filter."""
|
||||
response = app_client.get("/v1/models?backend=litellm")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# All models should be from litellm backend
|
||||
for model in data["data"]:
|
||||
assert model["backend"] == "litellm"
|
||||
|
||||
def test_list_models_invalid_backend(self, app_client: TestClient) -> None:
|
||||
"""Test GET /v1/models with invalid backend."""
|
||||
response = app_client.get("/v1/models?backend=invalid")
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_list_backends(self, app_client: TestClient) -> None:
|
||||
"""Test GET /v1/backends."""
|
||||
response = app_client.get("/v1/backends")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "backends" in data
|
||||
assert "litellm" in data["backends"]
|
||||
|
||||
|
||||
class TestCompletionsRoutes:
|
||||
"""Tests for completion routes."""
|
||||
|
||||
def test_chat_completions(
|
||||
self,
|
||||
app_client: TestClient,
|
||||
mock_completion_response: CompletionResponse,
|
||||
) -> None:
|
||||
"""Test POST /v1/chat/completions."""
|
||||
# Mock the client's complete method
|
||||
with patch.object(
|
||||
app_client.app.state.client,
|
||||
"complete",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_completion_response,
|
||||
):
|
||||
response = app_client.post(
|
||||
"/v1/chat/completions",
|
||||
json={
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"model": "gpt-3.5-turbo",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "id" in data
|
||||
assert "choices" in data
|
||||
assert "model" in data
|
||||
|
||||
def test_chat_completions_with_parameters(
|
||||
self,
|
||||
app_client: TestClient,
|
||||
mock_completion_response: CompletionResponse,
|
||||
) -> None:
|
||||
"""Test completion with optional parameters."""
|
||||
with patch.object(
|
||||
app_client.app.state.client,
|
||||
"complete",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_completion_response,
|
||||
):
|
||||
response = app_client.post(
|
||||
"/v1/chat/completions",
|
||||
json={
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"model": "gpt-4",
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 100,
|
||||
"top_p": 0.9,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_chat_completions_with_backend_override(
|
||||
self,
|
||||
app_client: TestClient,
|
||||
mock_completion_response: CompletionResponse,
|
||||
) -> None:
|
||||
"""Test completion with backend override."""
|
||||
with patch.object(
|
||||
app_client.app.state.client,
|
||||
"complete",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_completion_response,
|
||||
):
|
||||
response = app_client.post(
|
||||
"/v1/chat/completions",
|
||||
json={
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"model": "gpt-3.5-turbo",
|
||||
"backend": "litellm",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_chat_completions_missing_messages(self, app_client: TestClient) -> None:
|
||||
"""Test completion without messages returns error."""
|
||||
response = app_client.post(
|
||||
"/v1/chat/completions",
|
||||
json={"model": "gpt-3.5-turbo"},
|
||||
)
|
||||
|
||||
assert response.status_code == 422 # Validation error
|
||||
|
||||
def test_chat_completions_missing_model(self, app_client: TestClient) -> None:
|
||||
"""Test completion without model returns error."""
|
||||
response = app_client.post(
|
||||
"/v1/chat/completions",
|
||||
json={"messages": [{"role": "user", "content": "Hello"}]},
|
||||
)
|
||||
|
||||
assert response.status_code == 422 # Validation error
|
||||
|
||||
|
||||
class TestModelLoadRoutes:
|
||||
"""Tests for model load/unload routes."""
|
||||
|
||||
def test_load_model_unsupported_backend(self, app_client: TestClient) -> None:
|
||||
"""Test loading model on backend that doesn't support it."""
|
||||
response = app_client.post(
|
||||
"/v1/models/load",
|
||||
json={"model": "test-model", "backend": "litellm"},
|
||||
)
|
||||
|
||||
# LiteLLM doesn't support model loading
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_unload_model_unsupported_backend(self, app_client: TestClient) -> None:
|
||||
"""Test unloading model on backend that doesn't support it."""
|
||||
response = app_client.post(
|
||||
"/v1/models/unload",
|
||||
json={"model": "test-model", "backend": "litellm"},
|
||||
)
|
||||
|
||||
# LiteLLM doesn't support model unloading
|
||||
assert response.status_code == 400
|
||||
Loading…
Add table
Add a link
Reference in a new issue