Files
enclava/backend/tests/integration/test_llm_validation.py

296 lines
10 KiB
Python

"""
Simple validation tests for the new LLM service integration.
Tests basic functionality without complex mocking.
"""
import pytest
from httpx import AsyncClient
from unittest.mock import patch, MagicMock
class TestLLMServiceValidation:
"""Basic validation tests for LLM service integration."""
@pytest.mark.asyncio
async def test_llm_models_endpoint_exists(self, client: AsyncClient):
"""Test that the LLM models endpoint exists and is accessible."""
response = await client.get(
"/api/v1/llm/models",
headers={"Authorization": "Bearer test-api-key"}
)
# Should not return 404 (endpoint exists)
assert response.status_code != 404
# May return 500 or other error due to missing LLM service, but endpoint exists
@pytest.mark.asyncio
async def test_llm_chat_endpoint_exists(self, client: AsyncClient):
"""Test that the LLM chat endpoint exists and is accessible."""
response = await client.post(
"/api/v1/llm/chat/completions",
json={
"model": "test-model",
"messages": [
{"role": "user", "content": "Hello"}
]
},
headers={"Authorization": "Bearer test-api-key"}
)
# Should not return 404 (endpoint exists)
assert response.status_code != 404
@pytest.mark.asyncio
async def test_llm_embeddings_endpoint_exists(self, client: AsyncClient):
"""Test that the LLM embeddings endpoint exists and is accessible."""
response = await client.post(
"/api/v1/llm/embeddings",
json={
"model": "test-embedding-model",
"input": "Test text"
},
headers={"Authorization": "Bearer test-api-key"}
)
# Should not return 404 (endpoint exists)
assert response.status_code != 404
@pytest.mark.asyncio
async def test_llm_provider_status_endpoint_exists(self, client: AsyncClient):
"""Test that the provider status endpoint exists and is accessible."""
response = await client.get(
"/api/v1/llm/providers/status",
headers={"Authorization": "Bearer test-api-key"}
)
# Should not return 404 (endpoint exists)
assert response.status_code != 404
@pytest.mark.asyncio
async def test_chat_with_mocked_service(self, client: AsyncClient):
"""Test chat completion with mocked LLM service."""
from app.services.llm.models import ChatResponse, ChatChoice, ChatMessage, TokenUsage
# Mock successful response
mock_response = ChatResponse(
id="test-123",
object="chat.completion",
created=1234567890,
model="privatemode-llama-3-70b",
provider="PrivateMode.ai",
choices=[
ChatChoice(
index=0,
message=ChatMessage(
role="assistant",
content="Hello! How can I help you?"
),
finish_reason="stop"
)
],
usage=TokenUsage(
prompt_tokens=10,
completion_tokens=8,
total_tokens=18
),
security_check=True,
risk_score=0.1,
detected_patterns=[],
latency_ms=250.5
)
with patch("app.services.llm.service.llm_service.create_chat_completion") as mock_chat:
mock_chat.return_value = mock_response
response = await client.post(
"/api/v1/llm/chat/completions",
json={
"model": "privatemode-llama-3-70b",
"messages": [
{"role": "user", "content": "Hello"}
]
},
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
# Verify basic response structure
assert "id" in data
assert "choices" in data
assert len(data["choices"]) == 1
assert data["choices"][0]["message"]["content"] == "Hello! How can I help you?"
@pytest.mark.asyncio
async def test_embedding_with_mocked_service(self, client: AsyncClient):
"""Test embedding generation with mocked LLM service."""
from app.services.llm.models import EmbeddingResponse, EmbeddingData, TokenUsage
# Create a simple embedding vector
embedding_vector = [0.1, 0.2, 0.3] * 341 + [0.1, 0.2, 0.3] # 1024 dimensions
mock_response = EmbeddingResponse(
object="list",
data=[
EmbeddingData(
object="embedding",
index=0,
embedding=embedding_vector
)
],
model="privatemode-embeddings",
provider="PrivateMode.ai",
usage=TokenUsage(
prompt_tokens=5,
completion_tokens=0,
total_tokens=5
),
security_check=True,
risk_score=0.0,
latency_ms=150.0
)
with patch("app.services.llm.service.llm_service.create_embedding") as mock_embedding:
mock_embedding.return_value = mock_response
response = await client.post(
"/api/v1/llm/embeddings",
json={
"model": "privatemode-embeddings",
"input": "Test text for embedding"
},
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
# Verify basic response structure
assert "data" in data
assert len(data["data"]) == 1
assert len(data["data"][0]["embedding"]) == 1024
@pytest.mark.asyncio
async def test_models_with_mocked_service(self, client: AsyncClient):
"""Test models listing with mocked LLM service."""
from app.services.llm.models import ModelInfo
mock_models = [
ModelInfo(
id="privatemode-llama-3-70b",
object="model",
created=1234567890,
owned_by="PrivateMode.ai",
provider="PrivateMode.ai",
capabilities=["tee", "chat"],
context_window=32768,
supports_streaming=True
),
ModelInfo(
id="privatemode-embeddings",
object="model",
created=1234567890,
owned_by="PrivateMode.ai",
provider="PrivateMode.ai",
capabilities=["tee", "embeddings"],
context_window=512,
supports_streaming=False
)
]
with patch("app.services.llm.service.llm_service.get_models") as mock_models_call:
mock_models_call.return_value = mock_models
response = await client.get(
"/api/v1/llm/models",
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
# Verify basic response structure
assert "data" in data
assert len(data["data"]) == 2
assert data["data"][0]["id"] == "privatemode-llama-3-70b"
@pytest.mark.asyncio
async def test_provider_status_with_mocked_service(self, client: AsyncClient):
"""Test provider status with mocked LLM service."""
mock_status = {
"privatemode": {
"provider": "PrivateMode.ai",
"status": "healthy",
"latency_ms": 250.5,
"success_rate": 0.98,
"last_check": "2025-01-01T12:00:00Z",
"models_available": ["privatemode-llama-3-70b", "privatemode-embeddings"]
}
}
with patch("app.services.llm.service.llm_service.get_provider_status") as mock_provider:
mock_provider.return_value = mock_status
response = await client.get(
"/api/v1/llm/providers/status",
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
# Verify basic response structure
assert "data" in data
assert "privatemode" in data["data"]
assert data["data"]["privatemode"]["status"] == "healthy"
@pytest.mark.asyncio
async def test_unauthorized_access(self, client: AsyncClient):
"""Test that unauthorized requests are properly rejected."""
# Test without authorization header
response = await client.get("/api/v1/llm/models")
assert response.status_code == 401
response = await client.post(
"/api/v1/llm/chat/completions",
json={
"model": "test-model",
"messages": [{"role": "user", "content": "Hello"}]
}
)
assert response.status_code == 401
response = await client.post(
"/api/v1/llm/embeddings",
json={
"model": "test-model",
"input": "Hello"
}
)
assert response.status_code == 401
@pytest.mark.asyncio
async def test_invalid_request_data(self, client: AsyncClient):
"""Test that invalid request data is properly handled."""
# Test invalid JSON structure
response = await client.post(
"/api/v1/llm/chat/completions",
json={
# Missing required fields
"model": "test-model"
# messages field is missing
},
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 422 # Unprocessable Entity
# Test empty messages
response = await client.post(
"/api/v1/llm/chat/completions",
json={
"model": "test-model",
"messages": [] # Empty messages
},
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 422 # Unprocessable Entity