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.
This commit is contained in:
JSC
2025-08-28 22:37:43 +02:00
parent 7dee6e320e
commit 03abed6d39
23 changed files with 3415 additions and 103 deletions

189
app/schemas/scheduler.py Normal file
View File

@@ -0,0 +1,189 @@
"""Schemas for scheduled task API."""
from datetime import datetime
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field
from app.models.scheduled_task import RecurrenceType, TaskStatus, TaskType
class ScheduledTaskBase(BaseModel):
"""Base schema for scheduled tasks."""
name: str = Field(description="Human-readable task name")
task_type: TaskType = Field(description="Type of task to execute")
scheduled_at: datetime = Field(description="When the task should be executed")
timezone: str = Field(default="UTC", description="Timezone for scheduling")
parameters: Dict[str, Any] = Field(
default_factory=dict,
description="Task-specific parameters",
)
recurrence_type: RecurrenceType = Field(
default=RecurrenceType.NONE,
description="Recurrence pattern",
)
cron_expression: Optional[str] = Field(
default=None,
description="Cron expression for custom recurrence",
)
recurrence_count: Optional[int] = Field(
default=None,
description="Number of times to repeat (None for infinite)",
)
expires_at: Optional[datetime] = Field(
default=None,
description="When the task expires (optional)",
)
class ScheduledTaskCreate(ScheduledTaskBase):
"""Schema for creating a scheduled task."""
pass
class ScheduledTaskUpdate(BaseModel):
"""Schema for updating a scheduled task."""
name: Optional[str] = None
scheduled_at: Optional[datetime] = None
timezone: Optional[str] = None
parameters: Optional[Dict[str, Any]] = None
is_active: Optional[bool] = None
expires_at: Optional[datetime] = None
class ScheduledTaskResponse(ScheduledTaskBase):
"""Schema for scheduled task responses."""
id: int
status: TaskStatus
user_id: Optional[int] = None
executions_count: int
last_executed_at: Optional[datetime] = None
next_execution_at: Optional[datetime] = None
error_message: Optional[str] = None
is_active: bool
created_at: datetime
updated_at: datetime
class Config:
"""Pydantic configuration."""
from_attributes = True
# Task-specific parameter schemas
class CreditRechargeParameters(BaseModel):
"""Parameters for credit recharge tasks."""
user_id: Optional[int] = Field(
default=None,
description="Specific user ID to recharge (None for all users)",
)
class PlaySoundParameters(BaseModel):
"""Parameters for play sound tasks."""
sound_id: int = Field(description="ID of the sound to play")
class PlayPlaylistParameters(BaseModel):
"""Parameters for play playlist tasks."""
playlist_id: int = Field(description="ID of the playlist to play")
play_mode: str = Field(
default="continuous",
description="Play mode (continuous, loop, loop_one, random, single)",
)
shuffle: bool = Field(default=False, description="Whether to shuffle the playlist")
# Convenience schemas for creating specific task types
class CreateCreditRechargeTask(BaseModel):
"""Schema for creating credit recharge tasks."""
name: str = "Credit Recharge"
scheduled_at: datetime
timezone: str = "UTC"
recurrence_type: RecurrenceType = RecurrenceType.NONE
cron_expression: Optional[str] = None
recurrence_count: Optional[int] = None
expires_at: Optional[datetime] = None
user_id: Optional[int] = None
def to_task_create(self) -> ScheduledTaskCreate:
"""Convert to generic task creation schema."""
return ScheduledTaskCreate(
name=self.name,
task_type=TaskType.CREDIT_RECHARGE,
scheduled_at=self.scheduled_at,
timezone=self.timezone,
parameters={"user_id": self.user_id},
recurrence_type=self.recurrence_type,
cron_expression=self.cron_expression,
recurrence_count=self.recurrence_count,
expires_at=self.expires_at,
)
class CreatePlaySoundTask(BaseModel):
"""Schema for creating play sound tasks."""
name: str
scheduled_at: datetime
sound_id: int
timezone: str = "UTC"
recurrence_type: RecurrenceType = RecurrenceType.NONE
cron_expression: Optional[str] = None
recurrence_count: Optional[int] = None
expires_at: Optional[datetime] = None
def to_task_create(self) -> ScheduledTaskCreate:
"""Convert to generic task creation schema."""
return ScheduledTaskCreate(
name=self.name,
task_type=TaskType.PLAY_SOUND,
scheduled_at=self.scheduled_at,
timezone=self.timezone,
parameters={"sound_id": self.sound_id},
recurrence_type=self.recurrence_type,
cron_expression=self.cron_expression,
recurrence_count=self.recurrence_count,
expires_at=self.expires_at,
)
class CreatePlayPlaylistTask(BaseModel):
"""Schema for creating play playlist tasks."""
name: str
scheduled_at: datetime
playlist_id: int
play_mode: str = "continuous"
shuffle: bool = False
timezone: str = "UTC"
recurrence_type: RecurrenceType = RecurrenceType.NONE
cron_expression: Optional[str] = None
recurrence_count: Optional[int] = None
expires_at: Optional[datetime] = None
def to_task_create(self) -> ScheduledTaskCreate:
"""Convert to generic task creation schema."""
return ScheduledTaskCreate(
name=self.name,
task_type=TaskType.PLAY_PLAYLIST,
scheduled_at=self.scheduled_at,
timezone=self.timezone,
parameters={
"playlist_id": self.playlist_id,
"play_mode": self.play_mode,
"shuffle": self.shuffle,
},
recurrence_type=self.recurrence_type,
cron_expression=self.cron_expression,
recurrence_count=self.recurrence_count,
expires_at=self.expires_at,
)