mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
325 lines
11 KiB
Python
325 lines
11 KiB
Python
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
from rq.job import Job
|
|
|
|
from exceptions.task_exceptions import SchedulerException
|
|
from tasks.tasks import PeriodicTask, RemoteFilePullTask, TaskType, tasks_scheduler
|
|
|
|
|
|
class ConcretePeriodicTask(PeriodicTask):
|
|
"""Concrete implementation for testing abstract PeriodicTask"""
|
|
|
|
async def run(self, *args, **kwargs):
|
|
return "test_result"
|
|
|
|
|
|
class TestPeriodicTask:
|
|
@pytest.fixture
|
|
def task(self):
|
|
return ConcretePeriodicTask(
|
|
func="test.function",
|
|
title="Test Task",
|
|
description="test task",
|
|
task_type=TaskType.GENERIC,
|
|
enabled=True,
|
|
cron_string="0 0 * * *",
|
|
)
|
|
|
|
@pytest.fixture
|
|
def disabled_task(self):
|
|
return ConcretePeriodicTask(
|
|
func="test.disabled.function",
|
|
title="Disabled Task",
|
|
description="disabled task",
|
|
task_type=TaskType.GENERIC,
|
|
enabled=False,
|
|
cron_string="0 0 * * *",
|
|
)
|
|
|
|
def test_init(self, task):
|
|
"""Test task initialization"""
|
|
assert task.func == "test.function"
|
|
assert task.title == "Test Task"
|
|
assert task.description == "test task"
|
|
assert task.enabled is True
|
|
assert task.cron_string == "0 0 * * *"
|
|
|
|
@patch.object(tasks_scheduler, "get_jobs")
|
|
def test_get_existing_job_found(self, mock_get_jobs, task):
|
|
"""Test finding an existing job"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_job.func_name = "test.function"
|
|
mock_get_jobs.return_value = [mock_job]
|
|
|
|
result = task._get_existing_job()
|
|
assert result == mock_job
|
|
|
|
@patch.object(tasks_scheduler, "get_jobs")
|
|
def test_get_existing_job_not_found(self, mock_get_jobs, task):
|
|
"""Test when no existing job is found"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_job.func_name = "other.function"
|
|
mock_get_jobs.return_value = [mock_job]
|
|
|
|
result = task._get_existing_job()
|
|
assert result is None
|
|
|
|
@patch.object(tasks_scheduler, "get_jobs")
|
|
def test_get_existing_job_empty_list(self, mock_get_jobs, task):
|
|
"""Test when no jobs exist"""
|
|
mock_get_jobs.return_value = []
|
|
|
|
result = task._get_existing_job()
|
|
assert result is None
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch.object(ConcretePeriodicTask, "schedule")
|
|
@patch.object(ConcretePeriodicTask, "unschedule")
|
|
def test_init_enabled_no_existing_job(
|
|
self, mock_unschedule, mock_schedule, mock_get_existing_job, task
|
|
):
|
|
"""Test init when task is enabled and no existing job"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_get_existing_job.return_value = None
|
|
mock_schedule.return_value = mock_job
|
|
|
|
result = task.init()
|
|
|
|
mock_schedule.assert_called_once()
|
|
mock_unschedule.assert_not_called()
|
|
assert result == mock_job
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch.object(ConcretePeriodicTask, "schedule")
|
|
@patch.object(ConcretePeriodicTask, "unschedule")
|
|
def test_init_disabled_with_existing_job(
|
|
self, mock_unschedule, mock_schedule, mock_get_existing_job, disabled_task
|
|
):
|
|
"""Test init when task is disabled but has existing job"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_get_existing_job.return_value = mock_job
|
|
mock_unschedule.return_value = None
|
|
|
|
result = disabled_task.init()
|
|
|
|
mock_unschedule.assert_called_once()
|
|
mock_schedule.assert_not_called()
|
|
assert result is None
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch.object(ConcretePeriodicTask, "schedule")
|
|
@patch.object(ConcretePeriodicTask, "unschedule")
|
|
def test_init_enabled_with_existing_job(
|
|
self, mock_unschedule, mock_schedule, mock_get_existing_job, task
|
|
):
|
|
"""Test init when task is enabled and job already exists"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_get_existing_job.return_value = mock_job
|
|
|
|
result = task.init()
|
|
|
|
mock_schedule.assert_not_called()
|
|
mock_unschedule.assert_not_called()
|
|
assert result is None # Should do nothing
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch.object(tasks_scheduler, "cron")
|
|
def test_schedule_success(self, mock_cron, mock_get_existing_job, task):
|
|
"""Test successful scheduling"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_get_existing_job.return_value = None
|
|
mock_cron.return_value = mock_job
|
|
|
|
result = task.schedule()
|
|
|
|
mock_cron.assert_called_once_with(
|
|
"0 0 * * *",
|
|
func="test.function",
|
|
repeat=None,
|
|
timeout=5 * 60,
|
|
meta={"task_name": "Test Task", "task_type": "generic"},
|
|
)
|
|
assert result == mock_job
|
|
|
|
def test_schedule_not_enabled(self, disabled_task):
|
|
"""Test scheduling when task is not enabled"""
|
|
with pytest.raises(
|
|
SchedulerException, match="Scheduled disabled task is not enabled."
|
|
):
|
|
disabled_task.schedule()
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch("tasks.tasks.log")
|
|
def test_schedule_already_scheduled(self, mock_log, mock_get_existing_job, task):
|
|
"""Test scheduling when job already exists"""
|
|
mock_job = MagicMock()
|
|
mock_get_existing_job.return_value = mock_job
|
|
|
|
result = task.schedule()
|
|
|
|
mock_log.info.assert_called_once_with("Test task is already scheduled.")
|
|
assert result is None
|
|
|
|
def test_schedule_no_cron_string(self):
|
|
"""Test scheduling with no cron string"""
|
|
task = ConcretePeriodicTask(
|
|
func="test.function",
|
|
title="Test Task",
|
|
task_type=TaskType.GENERIC,
|
|
description="test task",
|
|
enabled=True,
|
|
cron_string=None,
|
|
)
|
|
|
|
with patch.object(task, "_get_existing_job", return_value=None):
|
|
result = task.schedule()
|
|
assert result is None
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch.object(tasks_scheduler, "cancel")
|
|
@patch("tasks.tasks.log")
|
|
def test_unschedule_success(
|
|
self, mock_log, mock_cancel, mock_get_existing_job, task
|
|
):
|
|
"""Test successful unscheduling"""
|
|
mock_job = MagicMock(spec=Job)
|
|
mock_get_existing_job.return_value = mock_job
|
|
|
|
task.unschedule()
|
|
|
|
mock_cancel.assert_called_once_with(mock_job)
|
|
mock_log.info.assert_called_once_with("Test task unscheduled.")
|
|
|
|
@patch.object(ConcretePeriodicTask, "_get_existing_job")
|
|
@patch("tasks.tasks.log")
|
|
def test_unschedule_not_scheduled(self, mock_log, mock_get_existing_job, task):
|
|
"""Test unscheduling when no job exists"""
|
|
mock_get_existing_job.return_value = None
|
|
|
|
task.unschedule()
|
|
|
|
mock_log.info.assert_called_once_with("Test task is not scheduled.")
|
|
|
|
async def test_run_abstract_method(self, task):
|
|
"""Test that run method works in concrete implementation"""
|
|
result = await task.run()
|
|
assert result == "test_result"
|
|
|
|
|
|
class TestRemoteFilePullTask:
|
|
@pytest.fixture
|
|
def task(self):
|
|
return RemoteFilePullTask(
|
|
func="test.remote.function",
|
|
title="Remote Test Task",
|
|
task_type=TaskType.UPDATE,
|
|
description="remote test task",
|
|
enabled=True,
|
|
cron_string="0 0 * * *",
|
|
url="https://example.com/data.json",
|
|
)
|
|
|
|
@pytest.fixture
|
|
def disabled_task(self):
|
|
return RemoteFilePullTask(
|
|
func="test.remote.disabled.function",
|
|
title="Disabled Remote Task",
|
|
task_type=TaskType.UPDATE,
|
|
description="disabled remote task",
|
|
enabled=False,
|
|
url="https://example.com/data.json",
|
|
)
|
|
|
|
def test_init(self, task):
|
|
"""Test RemoteFilePullTask initialization"""
|
|
assert task.func == "test.remote.function"
|
|
assert task.task_type == TaskType.UPDATE
|
|
assert task.description == "remote test task"
|
|
assert task.enabled is True
|
|
assert task.url == "https://example.com/data.json"
|
|
|
|
@patch("tasks.tasks.ctx_httpx_client")
|
|
@patch("tasks.tasks.log")
|
|
async def test_run_success(self, mock_log, mock_ctx_httpx_client, task):
|
|
"""Test successful remote file pull"""
|
|
mock_client = AsyncMock()
|
|
mock_response = MagicMock()
|
|
mock_response.content = b"test content"
|
|
mock_client.get.return_value = mock_response
|
|
mock_ctx_httpx_client.get.return_value = mock_client
|
|
|
|
result = await task.run(force=True)
|
|
|
|
mock_client.get.assert_called_once_with(
|
|
"https://example.com/data.json", timeout=120
|
|
)
|
|
mock_response.raise_for_status.assert_called_once()
|
|
mock_log.info.assert_called_once_with("Scheduled remote test task started...")
|
|
assert result == b"test content"
|
|
|
|
@patch("tasks.tasks.ctx_httpx_client")
|
|
@patch("tasks.tasks.log")
|
|
async def test_run_http_error(self, mock_log, mock_ctx_httpx_client, task):
|
|
"""Test handling of HTTP errors"""
|
|
mock_client = AsyncMock()
|
|
mock_client.get.side_effect = httpx.HTTPError("Connection failed")
|
|
mock_ctx_httpx_client.get.return_value = mock_client
|
|
|
|
result = await task.run(force=True)
|
|
|
|
mock_log.error.assert_called()
|
|
assert result is None
|
|
|
|
@patch("tasks.tasks.ctx_httpx_client")
|
|
@patch("tasks.tasks.log")
|
|
async def test_run_response_error(self, mock_log, mock_ctx_httpx_client, task):
|
|
"""Test handling of response status errors"""
|
|
mock_client = AsyncMock()
|
|
mock_response = MagicMock()
|
|
|
|
# Create a proper HTTPStatusError
|
|
http_error = httpx.HTTPStatusError(
|
|
"404 Not Found", request=MagicMock(), response=MagicMock()
|
|
)
|
|
mock_response.raise_for_status.side_effect = http_error
|
|
mock_client.get.return_value = mock_response
|
|
mock_ctx_httpx_client.get.return_value = mock_client
|
|
|
|
result = await task.run(force=True)
|
|
|
|
# Verify the specific error logging calls
|
|
mock_log.error.assert_any_call(
|
|
"Scheduled remote test task failed", exc_info=True
|
|
)
|
|
mock_log.error.assert_any_call(http_error)
|
|
assert result is None
|
|
|
|
@patch.object(RemoteFilePullTask, "unschedule")
|
|
@patch("tasks.tasks.log")
|
|
async def test_run_disabled_not_forced(
|
|
self, mock_log, mock_unschedule, disabled_task
|
|
):
|
|
"""Test run when task is disabled and not forced"""
|
|
result = await disabled_task.run(force=False)
|
|
|
|
mock_log.info.assert_called_once_with(
|
|
"Scheduled disabled remote task not enabled, unscheduling..."
|
|
)
|
|
mock_unschedule.assert_called_once()
|
|
assert result is None
|
|
|
|
@patch("tasks.tasks.ctx_httpx_client")
|
|
async def test_run_disabled_but_forced(self, mock_ctx_httpx_client, disabled_task):
|
|
"""Test run when task is disabled but forced"""
|
|
mock_client = AsyncMock()
|
|
mock_response = MagicMock()
|
|
mock_response.content = b"forced content"
|
|
mock_client.get.return_value = mock_response
|
|
mock_ctx_httpx_client.get.return_value = mock_client
|
|
|
|
result = await disabled_task.run(force=True)
|
|
|
|
assert result == b"forced content"
|