Download tests/integration/test_external_services.py from techprotrade/annator-command-center: direct link, hf CLI and curl.
- Browser
- Download file 30.7 kB
-
https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/integration/test_external_services.py
- Command line
-
hf download hf://spaces/techprotrade/annator-command-center/tests/integration/test_external_services.py
-
curl -L -o test_external_services.py https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/integration/test_external_services.py
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.""" | |
| 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 | |
| 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] | |
| 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] | |
| 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] | |
| 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.""" | |
| 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 | |
| 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] | |
| 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] | |
| 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.""" | |
| 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] | |
| 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] | |
| 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] | |
| 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] | |
| 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.""" | |
| 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] | |
| 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] | |
| 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] | |
| 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] | |
| 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.""" | |
| 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] | |
| 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] | |
| 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] | |
| 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.""" | |
| 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] | |
| 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] | |
| 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.""" | |
| 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] | |
| 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] | |
| 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.""" | |
| 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] | |
| 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.""" | |
| 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 | |
| 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] | |
| 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] | |