Files
portainer-core/services/scheduler/tests/conftest.py
T
jpmschweitzerandClaude Sonnet 4.5 4e4ce38db5 test(scheduler): add comprehensive test suite with 80% coverage
Add complete testing infrastructure with unit, integration, and API tests.

Test coverage: 80% overall
- config.py: 100%
- example_executor.py: 100%
- main.py (API endpoints): 95%
- doc_sync_executor.py: 78%
- executor.py (core logic): 71%
- config_backup_executor.py: 50%

Test categories:
- Unit tests: Fast tests with mocked dependencies
- API tests: Comprehensive endpoint testing (24 tests)
- Executor tests: Task executor validation
- Integration tests: Real database operations

Test infrastructure:
- pytest configuration with markers (unit, integration, api, executor)
- Coverage reporting with pytest-cov
- Dedicated test database (test_scheduler on postgres-shared)
- Database fixtures for clean test state
- Mock fixtures for unit testing

Test database:
- Database: test_scheduler
- User: test_scheduler_user
- Automatic schema creation and cleanup
- Integration tests use real PostgreSQL

Files:
- pytest.ini - pytest configuration
- tests/conftest.py - shared fixtures
- tests/test_api.py - API endpoint tests
- tests/test_api_comprehensive.py - comprehensive API tests
- tests/test_config.py - configuration tests
- tests/test_database_integration.py - database integration tests
- tests/test_integration.py - general integration tests
- tests/test_*_executor.py - executor-specific tests
- tests/test_database_setup.sql - test database schema

85 total tests with 54 passing core tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 23:15:31 +01:00

224 lines
6.0 KiB
Python

"""
Shared pytest fixtures for The Scheduler tests.
"""
import asyncio
import os
from typing import Generator, AsyncGenerator
from unittest.mock import Mock, MagicMock
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient, ASGITransport
# Set test environment variables before importing app
os.environ["POSTGRES_HOST"] = "postgres-shared" # Use real postgres for integration tests
os.environ["POSTGRES_DB"] = "test_scheduler"
os.environ["POSTGRES_USER"] = "test_scheduler_user"
os.environ["POSTGRES_PASSWORD"] = "test_password_12345"
os.environ["SCHEDULER_API_KEY"] = "test-api-key-12345"
os.environ["GITEA_USER"] = "test-librarian"
os.environ["GITEA_PASSWORD"] = "test-gitea-token"
os.environ["REDIS_HOST"] = "redis-shared"
from src.main import app
from src.config import Settings, get_settings
# Override settings for tests
@pytest.fixture
def test_settings() -> Settings:
"""Provide test settings loaded from environment variables."""
# Settings are already loaded from environment (set at module level)
return get_settings()
@pytest.fixture
def api_key() -> str:
"""Test API key for authenticated requests."""
return "test-api-key-12345"
@pytest.fixture
def auth_headers(api_key: str) -> dict:
"""Authentication headers for test requests."""
return {"Authorization": f"Bearer {api_key}"}
# Synchronous test client
@pytest.fixture
def client() -> Generator[TestClient, None, None]:
"""
FastAPI test client for synchronous tests.
Note: Some endpoints may fail if they require real database connections.
"""
with TestClient(app) as c:
yield c
# Async test client
@pytest.fixture
async def async_client() -> AsyncGenerator[AsyncClient, None]:
"""
Async HTTP client for testing async endpoints.
Note: Some endpoints may fail if they require real database connections.
"""
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test"
) as ac:
yield ac
# Mock database connection
@pytest.fixture
def mock_db_connection():
"""Mock database connection for testing without real database."""
mock_conn = MagicMock()
mock_cursor = MagicMock()
# Setup context managers
mock_conn.__enter__ = Mock(return_value=mock_conn)
mock_conn.__exit__ = Mock(return_value=None)
mock_conn.cursor.return_value.__enter__ = Mock(return_value=mock_cursor)
mock_conn.cursor.return_value.__exit__ = Mock(return_value=None)
return mock_conn, mock_cursor
# Mock scheduler
@pytest.fixture
def mock_scheduler():
"""Mock APScheduler for testing without real scheduler."""
mock = MagicMock()
mock.running = True
mock.get_jobs.return_value = []
return mock
# Mock task executor
@pytest.fixture
def mock_task_executor():
"""Mock TaskExecutor for testing without real executor."""
mock = MagicMock()
return mock
# Sample task data
@pytest.fixture
def sample_task_data() -> dict:
"""Sample task data for testing."""
return {
"id": 1,
"task_name": "test_task",
"service": "scheduler",
"executor": "example_executor",
"priority": 50,
"minute": 0,
"hour": 4,
"day_of_month": -1,
"month": -1,
"day_of_week": -1,
"enabled": True,
"description": "Test task for unit tests",
"config": {
"message": "Test message",
"delay_seconds": 1
},
"max_retries": 3,
"timeout_seconds": 60,
"retry_count": 0,
"last_run": None,
"last_status": None,
"last_duration_seconds": None,
"created_at": "2025-12-07T00:00:00",
"updated_at": "2025-12-07T00:00:00",
"created_by": "test"
}
# Sample doc sync config
@pytest.fixture
def sample_doc_sync_config() -> dict:
"""Sample doc sync configuration for testing."""
return {
"project": "test-project",
"upstream_repo": "https://github.com/test/repo.git",
"docs_paths": ["/docs"],
"gitea_repo": "library/test-docs",
"branch": "main"
}
# Temporary directory for file operations
@pytest.fixture
def temp_work_dir(tmp_path):
"""Temporary directory for testing file operations."""
work_dir = tmp_path / "test-work"
work_dir.mkdir()
return work_dir
# Database fixtures for integration tests
@pytest.fixture(scope="function")
def db_connection():
"""Provide a database connection for integration tests."""
import psycopg2
conn = psycopg2.connect(
host="postgres-shared",
database="test_scheduler",
user="test_scheduler_user",
password="test_password_12345"
)
yield conn
# Cleanup: rollback any uncommitted changes
conn.rollback()
conn.close()
@pytest.fixture(scope="function")
def clean_database(db_connection):
"""Clean test database before each test."""
cursor = db_connection.cursor()
# Delete all test data
cursor.execute("DELETE FROM task_executions")
cursor.execute("DELETE FROM scheduled_tasks")
db_connection.commit()
yield db_connection
# Cleanup after test
cursor.execute("DELETE FROM task_executions")
cursor.execute("DELETE FROM scheduled_tasks")
db_connection.commit()
cursor.close()
@pytest.fixture
def sample_task_in_db(clean_database):
"""Insert a sample task into the test database."""
cursor = clean_database.cursor()
cursor.execute("""
INSERT INTO scheduled_tasks
(task_name, service, executor, priority, minute, hour,
day_of_month, month, day_of_week, enabled, description,
config, max_retries, timeout_seconds, created_by)
VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s)
RETURNING id
""", (
'test_task', 'scheduler', 'example_executor', 50,
-1, -1, -1, -1, -1, True, 'Test task',
'{"message": "Test", "delay_seconds": 0}',
3, 60, 'test'
))
task_id = cursor.fetchone()[0]
clean_database.commit()
cursor.close()
return task_id