Files
sdb2-backend/app/api/v1/scheduler.py
JSC 03abed6d39 Add comprehensive tests for scheduled task repository, scheduler service, and task handlers
- Implemented tests for ScheduledTaskRepository covering task creation, retrieval, filtering, and status updates.
- Developed tests for SchedulerService including task creation, cancellation, user task retrieval, and maintenance jobs.
- Created tests for TaskHandlerRegistry to validate task execution for various types, including credit recharge and sound playback.
- Ensured proper error handling and edge cases in task execution scenarios.
- Added fixtures and mocks to facilitate isolated testing of services and repositories.
2025-08-28 22:37:43 +02:00

228 lines
8.1 KiB
Python

"""API endpoints for scheduled task management."""
from datetime import datetime
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.database import get_db
from app.core.dependencies import (
get_admin_user,
get_current_active_user,
)
from app.models.scheduled_task import RecurrenceType, ScheduledTask, TaskStatus, TaskType
from app.models.user import User
from app.schemas.scheduler import (
ScheduledTaskCreate,
ScheduledTaskResponse,
ScheduledTaskUpdate,
)
from app.services.scheduler import SchedulerService
router = APIRouter(prefix="/scheduler")
def get_scheduler_service() -> SchedulerService:
"""Get the global scheduler service instance."""
from app.main import get_global_scheduler_service
return get_global_scheduler_service()
@router.post("/tasks", response_model=ScheduledTaskResponse)
async def create_task(
task_data: ScheduledTaskCreate,
current_user: User = Depends(get_current_active_user),
scheduler_service: SchedulerService = Depends(get_scheduler_service),
) -> ScheduledTask:
"""Create a new scheduled task."""
try:
task = await scheduler_service.create_task(
name=task_data.name,
task_type=task_data.task_type,
scheduled_at=task_data.scheduled_at,
parameters=task_data.parameters,
user_id=current_user.id,
timezone=task_data.timezone,
recurrence_type=task_data.recurrence_type,
cron_expression=task_data.cron_expression,
recurrence_count=task_data.recurrence_count,
expires_at=task_data.expires_at,
)
return task
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/tasks", response_model=List[ScheduledTaskResponse])
async def get_user_tasks(
status: Optional[TaskStatus] = Query(None, description="Filter by task status"),
task_type: Optional[TaskType] = Query(None, description="Filter by task type"),
limit: Optional[int] = Query(50, description="Maximum number of tasks to return"),
offset: Optional[int] = Query(0, description="Number of tasks to skip"),
current_user: User = Depends(get_current_active_user),
scheduler_service: SchedulerService = Depends(get_scheduler_service),
) -> List[ScheduledTask]:
"""Get user's scheduled tasks."""
return await scheduler_service.get_user_tasks(
user_id=current_user.id,
status=status,
task_type=task_type,
limit=limit,
offset=offset,
)
@router.get("/tasks/{task_id}", response_model=ScheduledTaskResponse)
async def get_task(
task_id: int,
current_user: User = Depends(get_current_active_user),
db_session: AsyncSession = Depends(get_db),
) -> ScheduledTask:
"""Get a specific scheduled task."""
from app.repositories.scheduled_task import ScheduledTaskRepository
repo = ScheduledTaskRepository(db_session)
task = await repo.get_by_id(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Check if user owns the task or is admin
if task.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Access denied")
return task
@router.patch("/tasks/{task_id}", response_model=ScheduledTaskResponse)
async def update_task(
task_id: int,
task_update: ScheduledTaskUpdate,
current_user: User = Depends(get_current_active_user),
db_session: AsyncSession = Depends(get_db),
) -> ScheduledTask:
"""Update a scheduled task."""
from app.repositories.scheduled_task import ScheduledTaskRepository
repo = ScheduledTaskRepository(db_session)
task = await repo.get_by_id(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Check if user owns the task or is admin
if task.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Access denied")
# Update task fields
update_data = task_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(task, field, value)
updated_task = await repo.update(task)
return updated_task
@router.delete("/tasks/{task_id}")
async def cancel_task(
task_id: int,
current_user: User = Depends(get_current_active_user),
scheduler_service: SchedulerService = Depends(get_scheduler_service),
db_session: AsyncSession = Depends(get_db),
) -> dict:
"""Cancel a scheduled task."""
from app.repositories.scheduled_task import ScheduledTaskRepository
repo = ScheduledTaskRepository(db_session)
task = await repo.get_by_id(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Check if user owns the task or is admin
if task.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Access denied")
success = await scheduler_service.cancel_task(task_id)
if not success:
raise HTTPException(status_code=400, detail="Failed to cancel task")
return {"message": "Task cancelled successfully"}
# Admin-only endpoints
@router.get("/admin/tasks", response_model=List[ScheduledTaskResponse])
async def get_all_tasks(
status: Optional[TaskStatus] = Query(None, description="Filter by task status"),
task_type: Optional[TaskType] = Query(None, description="Filter by task type"),
limit: Optional[int] = Query(100, description="Maximum number of tasks to return"),
offset: Optional[int] = Query(0, description="Number of tasks to skip"),
current_user: User = Depends(get_admin_user),
db_session: AsyncSession = Depends(get_db),
) -> List[ScheduledTask]:
"""Get all scheduled tasks (admin only)."""
from app.repositories.scheduled_task import ScheduledTaskRepository
repo = ScheduledTaskRepository(db_session)
# Get all tasks with pagination and filtering
from sqlmodel import select
statement = select(ScheduledTask)
if status:
statement = statement.where(ScheduledTask.status == status)
if task_type:
statement = statement.where(ScheduledTask.task_type == task_type)
statement = statement.order_by(ScheduledTask.scheduled_at.desc())
if offset:
statement = statement.offset(offset)
if limit:
statement = statement.limit(limit)
result = await db_session.exec(statement)
return list(result.all())
@router.get("/admin/system-tasks", response_model=List[ScheduledTaskResponse])
async def get_system_tasks(
status: Optional[TaskStatus] = Query(None, description="Filter by task status"),
task_type: Optional[TaskType] = Query(None, description="Filter by task type"),
current_user: User = Depends(get_admin_user),
db_session: AsyncSession = Depends(get_db),
) -> List[ScheduledTask]:
"""Get system tasks (admin only)."""
from app.repositories.scheduled_task import ScheduledTaskRepository
repo = ScheduledTaskRepository(db_session)
return await repo.get_system_tasks(status=status, task_type=task_type)
@router.post("/admin/system-tasks", response_model=ScheduledTaskResponse)
async def create_system_task(
task_data: ScheduledTaskCreate,
current_user: User = Depends(get_admin_user),
scheduler_service: SchedulerService = Depends(get_scheduler_service),
) -> ScheduledTask:
"""Create a system task (admin only)."""
try:
task = await scheduler_service.create_task(
name=task_data.name,
task_type=task_data.task_type,
scheduled_at=task_data.scheduled_at,
parameters=task_data.parameters,
user_id=None, # System task
timezone=task_data.timezone,
recurrence_type=task_data.recurrence_type,
cron_expression=task_data.cron_expression,
recurrence_count=task_data.recurrence_count,
expires_at=task_data.expires_at,
)
return task
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))