""" Integration tests with database. These tests can be run with a test database or skipped if not available. """ import pytest import os from fastapi.testclient import TestClient from unittest.mock import patch @pytest.mark.integration @pytest.mark.skipif( os.getenv("RUN_INTEGRATION_TESTS") != "true", reason="Integration tests require RUN_INTEGRATION_TESTS=true" ) class TestDatabaseIntegration: """Integration tests that use actual database.""" def test_health_with_real_scheduler(self, client: TestClient): """Test health endpoint with real scheduler instance.""" # This test runs against the actual scheduler if it's running # For true integration testing, we'd set up a test database pass @pytest.mark.integration class TestEndToEndTaskFlow: """End-to-end tests for task lifecycle (mocked database).""" def test_create_list_delete_task_flow(self, client: TestClient, auth_headers: dict, sample_task_data: dict): """Test complete task lifecycle: create → list → delete.""" from unittest.mock import MagicMock # This simulates the full flow with mocked database with patch('src.main.get_task_executor') as mock_executor: mock_conn = MagicMock() mock_cursor = MagicMock() # Mock create created_task = {**sample_task_data, "id": 99} mock_cursor.fetchone.side_effect = [ created_task, # Create task created_task, # List tasks (as dict) ("test_task",) # Delete task ] mock_cursor.fetchall.return_value = [created_task] mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=mock_cursor) mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=None) mock_executor.return_value.get_db_connection.return_value.__enter__ = MagicMock(return_value=mock_conn) mock_executor.return_value.get_db_connection.return_value.__exit__ = MagicMock(return_value=None) # Create create_response = client.post( "/tasks", headers=auth_headers, json=sample_task_data ) # List list_response = client.get("/tasks", headers=auth_headers) # Delete delete_response = client.delete( "/tasks/test_task", headers=auth_headers ) # Verify the flow worked assert create_response.status_code in [200, 500] # May fail on DB issues assert list_response.status_code in [200, 500] assert delete_response.status_code in [200, 404, 500] @pytest.mark.integration class TestTaskExecutorIntegration: """Integration tests for task execution.""" @pytest.mark.asyncio async def test_execute_example_task_real(self, test_settings): """Test executing example executor with real implementation.""" from src.executors import example_executor config = { "message": "Integration test", "delay_seconds": 0 } result = await example_executor.execute(config, test_settings) assert "Integration test" in result assert "took" in result.lower() @pytest.mark.asyncio async def test_task_scheduling_logic(self, test_settings): """Test task scheduling logic.""" from src.tasks.executor import TaskExecutor from datetime import datetime executor = TaskExecutor(test_settings) # Test various scheduling scenarios task_every_minute = { 'minute': -1, 'hour': -1, 'day_of_month': -1, 'month': -1, 'day_of_week': -1 } task_specific_time = { 'minute': 30, 'hour': 14, 'day_of_month': -1, 'month': -1, 'day_of_week': -1 } now = datetime(2025, 12, 7, 14, 30, 0) # Every minute task should always run assert executor._should_run_now(task_every_minute, now) is True # Specific time task should run at 14:30 assert executor._should_run_now(task_specific_time, now) is True # But not at 14:31 now_plus_one = datetime(2025, 12, 7, 14, 31, 0) assert executor._should_run_now(task_specific_time, now_plus_one) is False @pytest.mark.integration class TestErrorHandling: """Integration tests for error handling.""" @pytest.mark.asyncio async def test_executor_with_invalid_module(self, test_settings, sample_task_data: dict): """Test task execution with invalid executor module.""" from src.tasks.executor import TaskExecutor executor = TaskExecutor(test_settings) task = {**sample_task_data, "executor": "nonexistent_executor"} with patch.object(executor, 'get_db_connection') as mock_get_conn: from unittest.mock import MagicMock mock_conn = MagicMock() mock_cursor = MagicMock() mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=mock_cursor) mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=None) mock_get_conn.return_value.__enter__ = MagicMock(return_value=mock_conn) mock_get_conn.return_value.__exit__ = MagicMock(return_value=None) # Should handle the error gracefully await executor.execute_task(task) # Should have recorded the error assert mock_cursor.execute.called @pytest.mark.integration class TestConfigValidation: """Integration tests for configuration validation.""" def test_settings_loads_from_environment(self): """Test that settings load correctly from environment.""" from src.config import get_settings settings = get_settings() # Should have loaded test environment variables assert settings.postgres_host == "test-postgres" assert settings.postgres_db == "test_scheduler" assert settings.scheduler_api_key == "test-api-key-12345" def test_settings_provides_database_url(self): """Test that settings provides correct database URL.""" from src.config import get_settings settings = get_settings() db_url = settings.database_url assert "postgresql://" in db_url assert "test_scheduler" in db_url def test_settings_provides_redis_url(self): """Test that settings provides correct Redis URL.""" from src.config import get_settings settings = get_settings() redis_url = settings.redis_url assert "redis://" in redis_url