annator-command-center / tests /integration /test_external_services.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
Raw
History Blame Contribute Delete
30.7 kB
"""
External service mocking integration tests (INTG-04).
Tests cover:
- LLM provider mocking (OpenAI, Anthropic)
- Slack integration mocking
- GitHub integration mocking
- Google OAuth mocking
- Error handling for external service failures
"""
import pytest
import responses
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from unittest.mock import Mock, AsyncMock, patch
import json
class TestOpenAIMocking:
"""Test OpenAI API mocking."""
@responses.activate
def test_openai_chat_completion_mock(self, client: TestClient, admin_token: str):
"""Test OpenAI chat completion with mocked response."""
# Mock OpenAI API
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={
"id": "chatcmpl-test123",
"object": "chat.completion",
"created": 1234567890,
"model": "gpt-4",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Mocked response from OpenAI"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15
}
},
status=200
)
# Test via LLM endpoint if it exists
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Endpoint may not be implemented yet
assert response.status_code in [200, 404, 405]
if response.status_code == 200:
data = response.json()
assert "content" in data or "choices" in data
@responses.activate
def test_openai_error_handling(self, client: TestClient, admin_token: str):
"""Test OpenAI API error handling."""
# Mock error response
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={
"error": {
"message": "Invalid API key",
"type": "invalid_request_error",
"code": "invalid_api_key"
}
},
status=401
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should handle error gracefully or endpoint not exist
assert response.status_code in [400, 401, 404, 405, 500, 502, 503]
@responses.activate
def test_openai_rate_limiting(self, client: TestClient, admin_token: str):
"""Test OpenAI rate limit handling."""
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error"
}
},
status=429
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [404, 405, 429, 503]
@responses.activate
def test_openai_streaming_response_mock(self, client: TestClient, admin_token: str):
"""Test OpenAI streaming response with mocked data."""
# Mock streaming endpoint
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
body="data: {\"choices\": [{\"delta\": {\"content\": \"Hello\"}}]}\n\n",
status=200,
content_type="text/event-stream"
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}],
"stream": True
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
@responses.activate
def test_openai_timeout_handling(self, client: TestClient, admin_token: str):
"""Test OpenAI timeout handling."""
# Add a callback that delays response
import time
def request_callback(request):
time.sleep(0.1) # Small delay to simulate timeout scenario
return (200, {}, json.dumps({"choices": [{"message": {"content": "Response"}}]}))
responses.add_callback(
responses.POST,
"https://api.openai.com/v1/chat/completions",
callback=request_callback,
content_type="application/json"
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=1.0
)
assert response.status_code in [200, 404, 405, 504]
class TestAnthropicMocking:
"""Test Anthropic API mocking."""
@responses.activate
def test_anthropic_message_mock(self, client: TestClient, admin_token: str):
"""Test Anthropic message API with mocked response."""
responses.add(
responses.POST,
"https://api.anthropic.com/v1/messages",
json={
"id": "msg_test123",
"type": "message",
"role": "assistant",
"content": [{"type": "text", "text": "Mocked Anthropic response"}],
"model": "claude-3-sonnet",
"stop_reason": "end_turn"
},
status=200
)
response = client.post(
"/api/llm/anthropic/message",
json={
"model": "claude-3-sonnet",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
if response.status_code == 200:
data = response.json()
assert "content" in data or "completion" in data
@responses.activate
def test_anthropic_error_handling(self, client: TestClient, admin_token: str):
"""Test Anthropic error handling."""
responses.add(
responses.POST,
"https://api.anthropic.com/v1/messages",
json={
"error": {
"message": "Invalid API key",
"type": "invalid_request_error"
}
},
status=401
)
response = client.post(
"/api/llm/anthropic/message",
json={
"model": "claude-3-sonnet",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [400, 401, 404, 405, 500]
@responses.activate
def test_anthropic_rate_limit(self, client: TestClient, admin_token: str):
"""Test Anthropic rate limit handling."""
responses.add(
responses.POST,
"https://api.anthropic.com/v1/messages",
json={
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error"
}
},
status=429
)
response = client.post(
"/api/llm/anthropic/message",
json={
"model": "claude-3-sonnet",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [404, 405, 429, 503]
@responses.activate
def test_anthropic_streaming_mock(self, client: TestClient, admin_token: str):
"""Test Anthropic streaming response."""
responses.add(
responses.POST,
"https://api.anthropic.com/v1/messages",
body='data: {"delta": {"type": "text_delta", "text": "Hello"}}\n\n',
status=200,
content_type="text/event-stream"
)
response = client.post(
"/api/llm/anthropic/message",
json={
"model": "claude-3-sonnet",
"messages": [{"role": "user", "content": "Hello"}],
"stream": True
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
class TestSlackIntegrationMocking:
"""Test Slack integration with mocking."""
@responses.activate
def test_slack_message_send_mock(self, client: TestClient, admin_token: str):
"""Test Slack message sending with mocked response."""
responses.add(
responses.POST,
"https://slack.com/api/chat.postMessage",
json={
"ok": True,
"channel": "C12345",
"ts": "1234567890.123456",
"message": {
"text": "Test message",
"username": "Test Bot"
}
},
status=200
)
response = client.post(
"/api/integrations/slack/send",
json={
"channel": "C12345",
"text": "Test message"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 201, 404, 405]
@responses.activate
def test_slack_error_handling(self, client: TestClient, admin_token: str):
"""Test Slack API error handling."""
responses.add(
responses.POST,
"https://slack.com/api/chat.postMessage",
json={
"ok": False,
"error": "channel_not_found"
},
status=200
)
response = client.post(
"/api/integrations/slack/send",
json={
"channel": "invalid-channel",
"text": "Test message"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should handle Slack errors
assert response.status_code in [400, 404, 405, 500]
@responses.activate
def test_slack_authentication_error(self, client: TestClient, admin_token: str):
"""Test Slack authentication error."""
responses.add(
responses.POST,
"https://slack.com/api/chat.postMessage",
json={
"ok": False,
"error": "invalid_auth"
},
status=200
)
response = client.post(
"/api/integrations/slack/send",
json={
"channel": "C12345",
"text": "Test message"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [400, 401, 404, 405, 500]
@responses.activate
def test_slack_rate_limiting(self, client: TestClient, admin_token: str):
"""Test Slack rate limiting."""
responses.add(
responses.POST,
"https://slack.com/api/chat.postMessage",
json={
"ok": False,
"error": "rate_limited"
},
status=429
)
response = client.post(
"/api/integrations/slack/send",
json={
"channel": "C12345",
"text": "Test message"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [404, 405, 429, 503]
@responses.activate
def test_slack_channel_list_mock(self, client: TestClient, admin_token: str):
"""Test Slack channel list with mocked response."""
responses.add(
responses.GET,
"https://slack.com/api/conversations.list",
json={
"ok": True,
"channels": [
{"id": "C12345", "name": "general"},
{"id": "C67890", "name": "random"}
]
},
status=200
)
response = client.get(
"/api/integrations/slack/channels",
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
class TestGitHubIntegrationMocking:
"""Test GitHub integration with mocking."""
@responses.activate
def test_github_issue_creation_mock(self, client: TestClient, admin_token: str):
"""Test GitHub issue creation with mocked response."""
responses.add(
responses.POST,
"https://api.github.com/repos/test/repo/issues",
json={
"id": 12345,
"number": 1,
"title": "Test Issue",
"state": "open",
"user": {"login": "testuser"}
},
status=201
)
response = client.post(
"/api/integrations/github/issues",
json={
"repo": "test/repo",
"title": "Test Issue",
"body": "Issue description"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 201, 404, 405]
@responses.activate
def test_github_authentication_error(self, client: TestClient, admin_token: str):
"""Test GitHub authentication error handling."""
responses.add(
responses.POST,
"https://api.github.com/repos/test/repo/issues",
json={
"message": "Bad credentials",
"documentation_url": "https://docs.github.com/rest"
},
status=401
)
response = client.post(
"/api/integrations/github/issues",
json={
"repo": "test/repo",
"title": "Test Issue"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [401, 404, 405, 502]
@responses.activate
def test_github_rate_limiting(self, client: TestClient, admin_token: str):
"""Test GitHub rate limit handling."""
responses.add(
responses.POST,
"https://api.github.com/repos/test/repo/issues",
json={
"message": "API rate limit exceeded",
"documentation_url": "https://docs.github.com/rest"
},
status=403
)
response = client.post(
"/api/integrations/github/issues",
json={
"repo": "test/repo",
"title": "Test Issue"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [403, 404, 405, 503]
@responses.activate
def test_github_repository_list_mock(self, client: TestClient, admin_token: str):
"""Test GitHub repository list with mocked response."""
responses.add(
responses.GET,
"https://api.github.com/user/repos",
json=[
{"id": 1, "name": "repo1", "full_name": "user/repo1"},
{"id": 2, "name": "repo2", "full_name": "user/repo2"}
],
status=200
)
response = client.get(
"/api/integrations/github/repos",
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
@responses.activate
def test_github_webhook_verification(self, client: TestClient, admin_token: str):
"""Test GitHub webhook signature verification."""
responses.add(
responses.POST,
"https://api.github.com/repos/test/repo/hooks",
json={
"id": 12345,
"name": "web",
"active": True
},
status=201
)
response = client.post(
"/api/integrations/github/webhooks",
json={
"repo": "test/repo",
"url": "https://example.com/webhook"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 201, 404, 405]
class TestGoogleOAuthMocking:
"""Test Google OAuth with mocking."""
@responses.activate
def test_google_oauth_flow_mock(self, client: TestClient):
"""Test Google OAuth flow with mocked responses."""
# Mock Google token endpoint
responses.add(
responses.POST,
"https://oauth2.googleapis.com/token",
json={
"access_token": "mock_access_token",
"expires_in": 3600,
"refresh_token": "mock_refresh_token",
"token_type": "Bearer"
},
status=200
)
# Mock Google user info endpoint
responses.add(
responses.GET,
"https://www.googleapis.com/oauth2/v2/userinfo",
json={
"id": "123456789",
"email": "test@example.com",
"verified_email": True,
"name": "Test User"
},
status=200
)
response = client.get(
"/api/auth/google/callback?code=test_code&state=test_state"
)
# Should complete OAuth flow or endpoint not exist
assert response.status_code in [200, 302, 404, 405]
@responses.activate
def test_google_oauth_error_handling(self, client: TestClient):
"""Test Google OAuth error handling."""
responses.add(
responses.POST,
"https://oauth2.googleapis.com/token",
json={
"error": "invalid_grant",
"error_description": "The code has expired."
},
status=400
)
response = client.get(
"/api/auth/google/callback?code=expired_code&state=test_state"
)
assert response.status_code in [400, 404, 405, 500]
@responses.activate
def test_google_oauth_invalid_state(self, client: TestClient):
"""Test Google OAuth with invalid state parameter."""
response = client.get(
"/api/auth/google/callback?code=test_code&state=invalid_state"
)
# Should reject invalid state
assert response.status_code in [400, 401, 403, 404, 405]
@responses.activate
def test_google_drive_api_mock(self, client: TestClient, admin_token: str):
"""Test Google Drive API with mocked response."""
responses.add(
responses.GET,
"https://www.googleapis.com/drive/v3/files",
json={
"files": [
{"id": "1", "name": "file1.txt"},
{"id": "2", "name": "file2.pdf"}
]
},
status=200
)
response = client.get(
"/api/integrations/google/files",
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
class TestExternalServiceTimeout:
"""Test timeout handling for external services."""
@responses.activate
def test_slow_llm_response_timeout(self, client: TestClient, admin_token: str):
"""Test timeout handling for slow LLM responses."""
# Add delayed response
import time
def request_callback(request):
time.sleep(0.1) # Simulate slow response (small delay for tests)
return (200, {}, json.dumps({"choices": [{"message": {"content": "Response"}}]}))
responses.add_callback(
responses.POST,
"https://api.openai.com/v1/chat/completions",
callback=request_callback,
content_type="application/json"
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should succeed or endpoint not exist
assert response.status_code in [200, 404, 405, 504]
@responses.activate
def test_external_service_unavailable(self, client: TestClient, admin_token: str):
"""Test handling when external service is unavailable."""
# Mock connection error
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
body=Exception("Connection refused"),
status=503
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should handle service unavailable gracefully
assert response.status_code in [404, 405, 503, 504]
@responses.activate
def test_external_service_dns_failure(self, client: TestClient, admin_token: str):
"""Test DNS failure handling."""
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={"error": {"message": "DNS resolution failed"}},
status=502
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [404, 405, 502, 503]
class TestLLMProviderFailover:
"""Test LLM provider failover logic."""
@responses.activate
def test_failover_to_backup_provider(self, client: TestClient, admin_token: str):
"""Test failover from primary to backup LLM provider."""
# Primary provider fails
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={"error": {"message": "Service unavailable"}},
status=503
)
# Backup provider succeeds
responses.add(
responses.POST,
"https://api.anthropic.com/v1/messages",
json={
"id": "msg_backup",
"content": [{"type": "text", "text": "Backup response"}]
},
status=200
)
response = client.post(
"/api/llm/chat",
json={
"messages": [{"role": "user", "content": "Hello"}],
"enable_failover": True
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should succeed or endpoint not exist
assert response.status_code in [200, 404, 405, 503]
@responses.activate
def test_all_providers_fail(self, client: TestClient, admin_token: str):
"""Test when all LLM providers fail."""
# All providers fail
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={"error": {"message": "Service unavailable"}},
status=503
)
responses.add(
responses.POST,
"https://api.anthropic.com/v1/messages",
json={"error": {"message": "Service unavailable"}},
status=503
)
response = client.post(
"/api/llm/chat",
json={
"messages": [{"role": "user", "content": "Hello"}],
"enable_failover": True
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should fail gracefully or endpoint not exist
assert response.status_code in [404, 405, 500, 503]
@responses.activate
def test_provider_selection_by_cost(self, client: TestClient, admin_token: str):
"""Test provider selection based on query complexity/cost."""
responses.add(
responses.POST,
"https://api.deepseek.com/v1/chat/completions",
json={
"choices": [{"message": {"content": "Budget response"}}]
},
status=200
)
response = client.post(
"/api/llm/chat",
json={
"messages": [{"role": "user", "content": "Simple question"}],
"cost_optimization": True
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 404, 405]
class TestMultiServiceIntegration:
"""Test integration across multiple external services."""
@responses.activate
def test_slack_and_github_integration(self, client: TestClient, admin_token: str):
"""Test coordinated Slack and GitHub integration."""
# Mock GitHub issue creation
responses.add(
responses.POST,
"https://api.github.com/repos/test/repo/issues",
json={
"id": 12345,
"number": 1,
"title": "Test Issue",
"state": "open"
},
status=201
)
# Mock Slack notification
responses.add(
responses.POST,
"https://slack.com/api/chat.postMessage",
json={
"ok": True,
"channel": "C12345",
"ts": "1234567890.123456"
},
status=200
)
response = client.post(
"/api/integrations/github-notify",
json={
"repo": "test/repo",
"title": "Test Issue",
"notify_slack": True,
"channel": "C12345"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 201, 404, 405]
@responses.activate
def test_oauth_with_multiple_providers(self, client: TestClient):
"""Test OAuth flow with multiple providers."""
# Mock Google
responses.add(
responses.POST,
"https://oauth2.googleapis.com/token",
json={
"access_token": "google_token",
"expires_in": 3600
},
status=200
)
# Mock GitHub
responses.add(
responses.POST,
"https://github.com/login/oauth/access_token",
json={
"access_token": "github_token",
"token_type": "bearer"
},
status=200
)
# Test Google OAuth
google_response = client.get(
"/api/auth/google/callback?code=test_code&state=test_state"
)
assert google_response.status_code in [200, 302, 404, 405]
# Test GitHub OAuth (if endpoint exists)
github_response = client.get(
"/api/auth/github/callback?code=test_code&state=test_state"
)
assert github_response.status_code in [200, 302, 404, 405]
class TestExternalServiceSecurity:
"""Test security aspects of external service integration."""
@responses.activate
def test_api_key_not_exposed(self, client: TestClient, admin_token: str):
"""Test that API keys are not exposed in responses."""
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={
"choices": [{"message": {"content": "Response"}}]
},
status=200
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Check that API keys are not leaked
if response.status_code == 200:
response_text = response.text.lower()
assert "sk-" not in response_text
assert "api_key" not in response_text or "hidden" in response_text
@responses.activate
def test_insecure_url_rejected(self, client: TestClient, admin_token: str):
"""Test that non-HTTPS URLs are rejected."""
response = client.post(
"/api/integrations/custom",
json={
"url": "http://example.com/api", # Not HTTPS
"data": {"test": "data"}
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should reject insecure URLs or endpoint not exist
assert response.status_code in [400, 404, 405]
@responses.activate
def test_malicious_payload_sanitized(self, client: TestClient, admin_token: str):
"""Test that malicious payloads are sanitized."""
malicious_payload = {
"message": "<script>alert('xss')</script>",
"query": "'; DROP TABLE users; --"
}
responses.add(
responses.POST,
"https://api.openai.com/v1/chat/completions",
json={
"choices": [{"message": {"content": "Safe response"}}]
},
status=200
)
response = client.post(
"/api/llm/openai/chat",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": malicious_payload["message"]}]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code in [200, 400, 404, 405]