diff --git a/src/tests/integration/test_performance_optimization.py b/src/tests/integration/test_performance_optimization.py new file mode 100644 index 0000000..32f959e --- /dev/null +++ b/src/tests/integration/test_performance_optimization.py @@ -0,0 +1,331 @@ +""" +Integration tests for performance optimization API endpoints. + +This module tests the performance-related endpoints for speed limiting, cache management, +memory management, and download task handling. +""" + +import pytest +from fastapi.testclient import TestClient +from unittest.mock import patch, Mock +import time + +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 TestSpeedLimitEndpoint: + """Test cases for /api/performance/speed-limit endpoint.""" + + def test_get_speed_limit_requires_auth(self, client): + """Test that getting speed limit requires authentication.""" + response = client.get("/api/performance/speed-limit") + assert response.status_code == 401 + + def test_set_speed_limit_requires_auth(self, client): + """Test that setting speed limit requires authentication.""" + response = client.post("/api/performance/speed-limit", json={"limit_mbps": 10}) + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_current_speed_limit(self, mock_user, client): + """Test getting current speed limit.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/performance/speed-limit") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "limit_mbps" in data + assert "current_usage_mbps" in data + + @patch('src.server.fastapi_app.get_current_user') + def test_set_speed_limit_valid(self, mock_user, client): + """Test setting valid speed limit.""" + mock_user.return_value = {"user_id": "test_user"} + + limit_data = {"limit_mbps": 50} + response = client.post("/api/performance/speed-limit", json=limit_data) + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + def test_set_speed_limit_invalid(self, client, auth_headers): + """Test setting invalid speed limit.""" + invalid_limits = [ + {"limit_mbps": -1}, # Negative + {"limit_mbps": 0}, # Zero + {"limit_mbps": "invalid"}, # Non-numeric + ] + + for limit_data in invalid_limits: + response = client.post("/api/performance/speed-limit", json=limit_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +class TestCacheStatsEndpoint: + """Test cases for /api/performance/cache/stats endpoint.""" + + def test_cache_stats_requires_auth(self, client): + """Test that cache stats requires authentication.""" + response = client.get("/api/performance/cache/stats") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_cache_stats(self, mock_user, client): + """Test getting cache statistics.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/performance/cache/stats") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + expected_fields = ["hit_rate", "miss_rate", "size_bytes", "entries_count", "evictions"] + for field in expected_fields: + assert field in data + + @patch('src.server.fastapi_app.get_current_user') + def test_clear_cache(self, mock_user, client): + """Test clearing cache.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.delete("/api/performance/cache/stats") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + +class TestMemoryStatsEndpoint: + """Test cases for /api/performance/memory/stats endpoint.""" + + def test_memory_stats_requires_auth(self, client): + """Test that memory stats requires authentication.""" + response = client.get("/api/performance/memory/stats") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_memory_stats(self, mock_user, client): + """Test getting memory statistics.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/performance/memory/stats") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + expected_fields = ["used_bytes", "available_bytes", "percent_used", "process_memory"] + for field in expected_fields: + assert field in data + + +class TestMemoryGCEndpoint: + """Test cases for /api/performance/memory/gc endpoint.""" + + def test_memory_gc_requires_auth(self, client): + """Test that memory garbage collection requires authentication.""" + response = client.post("/api/performance/memory/gc") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_trigger_garbage_collection(self, mock_user, client): + """Test triggering garbage collection.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.post("/api/performance/memory/gc") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "collected_objects" in data + assert "memory_freed_bytes" in data + + +class TestDownloadTasksEndpoint: + """Test cases for /api/performance/downloads/tasks endpoint.""" + + def test_download_tasks_requires_auth(self, client): + """Test that download tasks requires authentication.""" + response = client.get("/api/performance/downloads/tasks") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_get_download_tasks(self, mock_user, client): + """Test getting download tasks.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/performance/downloads/tasks") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "tasks" in data + assert isinstance(data["tasks"], list) + + @patch('src.server.fastapi_app.get_current_user') + def test_get_download_tasks_with_status_filter(self, mock_user, client): + """Test getting download tasks with status filter.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.get("/api/performance/downloads/tasks?status=active") + assert response.status_code in [200, 404] + + response = client.get("/api/performance/downloads/tasks?status=completed") + assert response.status_code in [200, 404] + + +class TestAddDownloadTaskEndpoint: + """Test cases for /api/performance/downloads/add-task endpoint.""" + + def test_add_download_task_requires_auth(self, client): + """Test that adding download task requires authentication.""" + response = client.post("/api/performance/downloads/add-task", json={"anime_id": "test"}) + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_add_download_task_valid(self, mock_user, client): + """Test adding valid download task.""" + mock_user.return_value = {"user_id": "test_user"} + + task_data = { + "anime_id": "anime123", + "episode_range": {"start": 1, "end": 12}, + "quality": "1080p", + "priority": "normal" + } + + response = client.post("/api/performance/downloads/add-task", json=task_data) + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "task_id" in data + assert "status" in data + + def test_add_download_task_invalid(self, client, auth_headers): + """Test adding invalid download task.""" + invalid_tasks = [ + {}, # Empty data + {"anime_id": ""}, # Empty anime_id + {"anime_id": "test", "episode_range": {"start": 5, "end": 2}}, # Invalid range + ] + + for task_data in invalid_tasks: + response = client.post("/api/performance/downloads/add-task", json=task_data, headers=auth_headers) + assert response.status_code in [400, 404, 422] + + +class TestResumeTasksEndpoint: + """Test cases for /api/performance/resume/tasks endpoint.""" + + def test_resume_tasks_requires_auth(self, client): + """Test that resuming tasks requires authentication.""" + response = client.post("/api/performance/resume/tasks") + assert response.status_code == 401 + + @patch('src.server.fastapi_app.get_current_user') + def test_resume_all_tasks(self, mock_user, client): + """Test resuming all paused tasks.""" + mock_user.return_value = {"user_id": "test_user"} + + response = client.post("/api/performance/resume/tasks") + # Expected 404 since endpoint not implemented yet + assert response.status_code in [200, 404] + + if response.status_code == 200: + data = response.json() + assert "resumed_count" in data + + @patch('src.server.fastapi_app.get_current_user') + def test_resume_specific_task(self, mock_user, client): + """Test resuming specific task.""" + mock_user.return_value = {"user_id": "test_user"} + + task_data = {"task_id": "task123"} + response = client.post("/api/performance/resume/tasks", json=task_data) + assert response.status_code in [200, 404] + + +class TestPerformanceEndpointsIntegration: + """Integration tests for performance endpoints.""" + + @patch('src.server.fastapi_app.get_current_user') + def test_performance_workflow(self, mock_user, client): + """Test typical performance monitoring workflow.""" + mock_user.return_value = {"user_id": "test_user"} + + # 1. Check current memory stats + response = client.get("/api/performance/memory/stats") + assert response.status_code in [200, 404] + + # 2. Check cache stats + response = client.get("/api/performance/cache/stats") + assert response.status_code in [200, 404] + + # 3. Check download tasks + response = client.get("/api/performance/downloads/tasks") + assert response.status_code in [200, 404] + + # 4. If needed, trigger garbage collection + response = client.post("/api/performance/memory/gc") + assert response.status_code in [200, 404] + + def test_performance_endpoints_error_handling(self, client, auth_headers): + """Test error handling across performance endpoints.""" + # Test various endpoints with malformed requests + endpoints_methods = [ + ("GET", "/api/performance/memory/stats"), + ("GET", "/api/performance/cache/stats"), + ("GET", "/api/performance/downloads/tasks"), + ("POST", "/api/performance/memory/gc"), + ("POST", "/api/performance/resume/tasks"), + ] + + for method, endpoint in endpoints_methods: + if method == "GET": + response = client.get(endpoint, headers=auth_headers) + else: + response = client.post(endpoint, headers=auth_headers) + + # Should either work (200) or not be implemented yet (404) + assert response.status_code in [200, 404] + + @patch('src.server.fastapi_app.get_current_user') + def test_concurrent_performance_requests(self, mock_user, client): + """Test handling of concurrent performance requests.""" + mock_user.return_value = {"user_id": "test_user"} + + # This would test actual concurrency in a real implementation + # For now, just verify endpoints are accessible + response = client.get("/api/performance/memory/stats") + assert response.status_code in [200, 404] + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) \ No newline at end of file