Add integration tests for API key management, webhooks, and third-party services - Created comprehensive test suite for integration endpoints - Includes tests for API key CRUD operations and permissions - Tests webhook configuration, testing, and management - Covers third-party service integrations (Discord, etc) - Tests security features like API key validation and rate limiting - Ready for future integration endpoint implementation

This commit is contained in:
Lukas Pupka-Lipinski 2025-10-06 11:33:02 +02:00
parent 733c86eb6b
commit e95ed299d6

View File

@ -0,0 +1,439 @@
"""
Integration tests for API key management, webhooks, and third-party integrations.
This module tests the integration endpoints for managing API keys, webhook configurations,
and third-party service integrations.
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, Mock
import json
import uuid
from src.server.fastapi_app import app
@pytest.fixture
def client():
"""Create a test client for the FastAPI application."""
return TestClient(app)
@pytest.fixture
def auth_headers(client):
"""Provide authentication headers for protected endpoints."""
# Login to get token
login_data = {"password": "testpassword"}
with patch('src.server.fastapi_app.settings.master_password_hash') as mock_hash:
mock_hash.return_value = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # 'password' hash
response = client.post("/auth/login", json=login_data)
if response.status_code == 200:
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
return {}
class TestAPIKeyManagement:
"""Test cases for API key management endpoints."""
def test_list_api_keys_requires_auth(self, client):
"""Test that listing API keys requires authentication."""
response = client.get("/api/integrations/api-keys")
assert response.status_code == 401
def test_create_api_key_requires_auth(self, client):
"""Test that creating API keys requires authentication."""
response = client.post("/api/integrations/api-keys", json={"name": "test_key"})
assert response.status_code == 401
@patch('src.server.fastapi_app.get_current_user')
def test_list_api_keys(self, mock_user, client):
"""Test listing API keys."""
mock_user.return_value = {"user_id": "test_user"}
response = client.get("/api/integrations/api-keys")
# Expected 404 since endpoint not implemented yet
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "api_keys" in data
assert isinstance(data["api_keys"], list)
@patch('src.server.fastapi_app.get_current_user')
def test_create_api_key(self, mock_user, client):
"""Test creating new API key."""
mock_user.return_value = {"user_id": "test_user"}
key_data = {
"name": "test_integration_key",
"description": "Key for testing integrations",
"permissions": ["read", "write"],
"expires_at": "2024-12-31T23:59:59Z"
}
response = client.post("/api/integrations/api-keys", json=key_data)
# Expected 404 since endpoint not implemented yet
assert response.status_code in [201, 404]
if response.status_code == 201:
data = response.json()
assert "api_key_id" in data
assert "api_key" in data
assert "created_at" in data
@patch('src.server.fastapi_app.get_current_user')
def test_get_api_key_details(self, mock_user, client):
"""Test getting API key details."""
mock_user.return_value = {"user_id": "test_user"}
key_id = "test_key_123"
response = client.get(f"/api/integrations/api-keys/{key_id}")
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "api_key_id" in data
assert "name" in data
assert "permissions" in data
assert "created_at" in data
@patch('src.server.fastapi_app.get_current_user')
def test_revoke_api_key(self, mock_user, client):
"""Test revoking API key."""
mock_user.return_value = {"user_id": "test_user"}
key_id = "test_key_123"
response = client.delete(f"/api/integrations/api-keys/{key_id}")
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "status" in data
assert data["status"] == "revoked"
def test_create_api_key_invalid_data(self, client, auth_headers):
"""Test creating API key with invalid data."""
invalid_data_sets = [
{}, # Empty data
{"name": ""}, # Empty name
{"name": "test", "permissions": []}, # Empty permissions
{"name": "test", "expires_at": "invalid_date"}, # Invalid date
]
for invalid_data in invalid_data_sets:
response = client.post("/api/integrations/api-keys", json=invalid_data, headers=auth_headers)
assert response.status_code in [400, 404, 422]
@patch('src.server.fastapi_app.get_current_user')
def test_update_api_key_permissions(self, mock_user, client):
"""Test updating API key permissions."""
mock_user.return_value = {"user_id": "test_user"}
key_id = "test_key_123"
update_data = {
"permissions": ["read"],
"description": "Updated description"
}
response = client.patch(f"/api/integrations/api-keys/{key_id}", json=update_data)
assert response.status_code in [200, 404]
class TestWebhookManagement:
"""Test cases for webhook configuration endpoints."""
def test_list_webhooks_requires_auth(self, client):
"""Test that listing webhooks requires authentication."""
response = client.get("/api/integrations/webhooks")
assert response.status_code == 401
@patch('src.server.fastapi_app.get_current_user')
def test_list_webhooks(self, mock_user, client):
"""Test listing configured webhooks."""
mock_user.return_value = {"user_id": "test_user"}
response = client.get("/api/integrations/webhooks")
# Expected 404 since endpoint not implemented yet
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "webhooks" in data
assert isinstance(data["webhooks"], list)
@patch('src.server.fastapi_app.get_current_user')
def test_create_webhook(self, mock_user, client):
"""Test creating new webhook."""
mock_user.return_value = {"user_id": "test_user"}
webhook_data = {
"name": "download_complete_webhook",
"url": "https://example.com/webhook",
"events": ["download_complete", "download_failed"],
"secret": "webhook_secret_123",
"active": True
}
response = client.post("/api/integrations/webhooks", json=webhook_data)
# Expected 404 since endpoint not implemented yet
assert response.status_code in [201, 404]
if response.status_code == 201:
data = response.json()
assert "webhook_id" in data
assert "created_at" in data
@patch('src.server.fastapi_app.get_current_user')
def test_test_webhook(self, mock_user, client):
"""Test webhook endpoint."""
mock_user.return_value = {"user_id": "test_user"}
webhook_id = "webhook_123"
test_data = {
"event_type": "test",
"test_payload": {"message": "test webhook"}
}
response = client.post(f"/api/integrations/webhooks/{webhook_id}/test", json=test_data)
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "status" in data
assert "response_time_ms" in data
@patch('src.server.fastapi_app.get_current_user')
def test_update_webhook(self, mock_user, client):
"""Test updating webhook configuration."""
mock_user.return_value = {"user_id": "test_user"}
webhook_id = "webhook_123"
update_data = {
"active": False,
"events": ["download_complete"]
}
response = client.patch(f"/api/integrations/webhooks/{webhook_id}", json=update_data)
assert response.status_code in [200, 404]
@patch('src.server.fastapi_app.get_current_user')
def test_delete_webhook(self, mock_user, client):
"""Test deleting webhook."""
mock_user.return_value = {"user_id": "test_user"}
webhook_id = "webhook_123"
response = client.delete(f"/api/integrations/webhooks/{webhook_id}")
assert response.status_code in [200, 404]
def test_create_webhook_invalid_url(self, client, auth_headers):
"""Test creating webhook with invalid URL."""
invalid_webhook_data = {
"name": "invalid_webhook",
"url": "not_a_valid_url",
"events": ["download_complete"]
}
response = client.post("/api/integrations/webhooks", json=invalid_webhook_data, headers=auth_headers)
assert response.status_code in [400, 404, 422]
class TestThirdPartyIntegrations:
"""Test cases for third-party service integrations."""
def test_list_integrations_requires_auth(self, client):
"""Test that listing integrations requires authentication."""
response = client.get("/api/integrations/services")
assert response.status_code == 401
@patch('src.server.fastapi_app.get_current_user')
def test_list_available_integrations(self, mock_user, client):
"""Test listing available third-party integrations."""
mock_user.return_value = {"user_id": "test_user"}
response = client.get("/api/integrations/services")
# Expected 404 since endpoint not implemented yet
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "services" in data
assert isinstance(data["services"], list)
@patch('src.server.fastapi_app.get_current_user')
def test_configure_integration(self, mock_user, client):
"""Test configuring third-party integration."""
mock_user.return_value = {"user_id": "test_user"}
service_name = "discord"
config_data = {
"webhook_url": "https://discord.com/api/webhooks/...",
"notifications": ["download_complete", "series_added"],
"enabled": True
}
response = client.post(f"/api/integrations/services/{service_name}/configure", json=config_data)
assert response.status_code in [200, 404]
@patch('src.server.fastapi_app.get_current_user')
def test_test_integration(self, mock_user, client):
"""Test third-party integration."""
mock_user.return_value = {"user_id": "test_user"}
service_name = "discord"
test_data = {
"message": "Test notification from AniWorld"
}
response = client.post(f"/api/integrations/services/{service_name}/test", json=test_data)
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "status" in data
assert "response" in data
@patch('src.server.fastapi_app.get_current_user')
def test_get_integration_status(self, mock_user, client):
"""Test getting integration status."""
mock_user.return_value = {"user_id": "test_user"}
service_name = "discord"
response = client.get(f"/api/integrations/services/{service_name}/status")
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "service" in data
assert "status" in data
assert "last_tested" in data
@patch('src.server.fastapi_app.get_current_user')
def test_disable_integration(self, mock_user, client):
"""Test disabling integration."""
mock_user.return_value = {"user_id": "test_user"}
service_name = "discord"
response = client.post(f"/api/integrations/services/{service_name}/disable")
assert response.status_code in [200, 404]
class TestIntegrationEvents:
"""Test cases for integration event handling."""
@patch('src.server.fastapi_app.get_current_user')
def test_list_integration_events(self, mock_user, client):
"""Test listing integration events."""
mock_user.return_value = {"user_id": "test_user"}
response = client.get("/api/integrations/events")
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "events" in data
assert isinstance(data["events"], list)
@patch('src.server.fastapi_app.get_current_user')
def test_trigger_test_event(self, mock_user, client):
"""Test triggering test integration event."""
mock_user.return_value = {"user_id": "test_user"}
event_data = {
"event_type": "download_complete",
"payload": {
"anime_id": "test_anime",
"episode_count": 12,
"download_time": "2023-01-01T12:00:00Z"
}
}
response = client.post("/api/integrations/events/trigger", json=event_data)
assert response.status_code in [200, 404]
@patch('src.server.fastapi_app.get_current_user')
def test_get_event_history(self, mock_user, client):
"""Test getting integration event history."""
mock_user.return_value = {"user_id": "test_user"}
response = client.get("/api/integrations/events/history")
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.json()
assert "events" in data
assert "pagination" in data
class TestIntegrationSecurity:
"""Test cases for integration security features."""
@patch('src.server.fastapi_app.get_current_user')
def test_api_key_validation(self, mock_user, client):
"""Test API key validation."""
mock_user.return_value = {"user_id": "test_user"}
# Test with valid API key format
validation_data = {
"api_key": "ak_test_" + str(uuid.uuid4()).replace("-", "")
}
response = client.post("/api/integrations/validate-key", json=validation_data)
assert response.status_code in [200, 404]
@patch('src.server.fastapi_app.get_current_user')
def test_webhook_signature_validation(self, mock_user, client):
"""Test webhook signature validation."""
mock_user.return_value = {"user_id": "test_user"}
signature_data = {
"payload": {"test": "data"},
"signature": "sha256=test_signature",
"secret": "webhook_secret"
}
response = client.post("/api/integrations/validate-signature", json=signature_data)
assert response.status_code in [200, 404]
def test_integration_rate_limiting(self, client, auth_headers):
"""Test rate limiting for integration endpoints."""
# Make multiple rapid requests to test rate limiting
for i in range(10):
response = client.get("/api/integrations/api-keys", headers=auth_headers)
# Should either work or be rate limited
assert response.status_code in [200, 404, 429]
class TestIntegrationErrorHandling:
"""Test cases for integration error handling."""
def test_invalid_service_name(self, client, auth_headers):
"""Test handling of invalid service names."""
response = client.get("/api/integrations/services/invalid_service/status", headers=auth_headers)
assert response.status_code in [400, 404]
def test_malformed_webhook_payload(self, client, auth_headers):
"""Test handling of malformed webhook payloads."""
malformed_data = {
"url": "https://example.com",
"events": "not_a_list" # Should be a list
}
response = client.post("/api/integrations/webhooks", json=malformed_data, headers=auth_headers)
assert response.status_code in [400, 404, 422]
@patch('src.server.fastapi_app.get_current_user')
def test_integration_service_unavailable(self, mock_user, client):
"""Test handling when integration service is unavailable."""
mock_user.return_value = {"user_id": "test_user"}
# This would test actual service connectivity in real implementation
response = client.post("/api/integrations/services/discord/test", json={"message": "test"})
assert response.status_code in [200, 404, 503]
if __name__ == "__main__":
pytest.main([__file__, "-v"])