from unittest.mock import AsyncMock, MagicMock from uuid import UUID import pytest from pytest_mock import MockerFixture from product_staging.api import datasources from product_staging.connectors.redis import RedisConnector from product_staging.models import task from product_staging.models.task import Task, TaskStatus token = UUID("bfdf2dca-b678-47d0-9c7e-4d3af939593d") identity_uuid = UUID("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d") payload = {"details": "about", "the": "task"} fixed_time = 1744300000.0 async def test_create_task(mocker: MockerFixture) -> None: """ Test create_task function. create_task should call set on Redis with a token key, a Task object serialized to JSON, and a TTL. It should return a Task object with the expected token and status. """ mock_set = AsyncMock() client = MagicMock(spec=RedisConnector, set=mock_set) mocker.patch("product_staging.models.task.uuid.uuid4", return_value=token) mocker.patch("product_staging.models.task.time.time", return_value=fixed_time) mocker.patch.object(datasources, "get_redis_client", return_value=client) result = await task.create_task(identity_uuid=identity_uuid) expected_task = Task( status="in_progress", token=token, payload=None, created_by=identity_uuid, created_at=fixed_time, ) mock_set.assert_called_once_with( key=task.token_key(token), item=expected_task.model_dump(mode="json"), ttl=task.TASK_TTL, ) assert result.token == token assert result.status == "in_progress" assert result.payload is None assert result.created_by == identity_uuid assert result.created_at == fixed_time async def test_create_task_without_identity(mocker: MockerFixture) -> None: """ Test create_task without identity_uuid defaults to None for created_by. """ mock_set = AsyncMock() client = MagicMock(spec=RedisConnector, set=mock_set) mocker.patch("product_staging.models.task.uuid.uuid4", return_value=token) mocker.patch("product_staging.models.task.time.time", return_value=fixed_time) mocker.patch.object(datasources, "get_redis_client", return_value=client) result = await task.create_task() assert result.token == token assert result.status == "in_progress" assert result.created_by is None assert result.created_at == fixed_time async def test_get_task_does_not_exist(mocker: MockerFixture) -> None: """ Test get_task function returns None when the task does not exist in Redis. """ client = MagicMock(spec=RedisConnector) mock_get = AsyncMock(return_value=None) client.get = mock_get mocker.patch.object(datasources, "get_redis_client", return_value=client) result = await task.get_task(token=token) assert result is None mock_get.assert_called_once_with(key=task.token_key(token)) async def test_get_task_exists(mocker: MockerFixture) -> None: """ Test get_task function returns a Task object when the task exists in Redis. """ existing_task = Task( token=token, status="in_progress", payload={"key": "value"}, created_at=fixed_time, ) client = MagicMock(spec=RedisConnector) mock_get = AsyncMock(return_value=existing_task.model_dump(mode="json")) client.get = mock_get mocker.patch.object(datasources, "get_redis_client", return_value=client) result = await task.get_task(token=token) assert result == existing_task mock_get.assert_called_once_with(key=task.token_key(token)) async def test_get_task_exists_with_status_filter(mocker: MockerFixture) -> None: """ Test get_task function returns a Task object when the task exists in Redis and matches the status filter. """ existing_task = Task( token=token, status="in_progress", payload={"key": "value"}, created_at=fixed_time, ) client = MagicMock(spec=RedisConnector) mock_get = AsyncMock(return_value=existing_task.model_dump(mode="json")) client.get = mock_get mocker.patch.object(datasources, "get_redis_client", return_value=client) result = await task.get_task(token=token, status="in_progress") assert result == existing_task mock_get.assert_called_once_with(key=task.token_key(token)) async def test_get_task_exists_with_status_filter_no_match( mocker: MockerFixture, ) -> None: """ Test get_task function returns None when the task exists in Redis but does not match the status filter. """ existing_task = Task( token=token, status="in_progress", payload={"key": "value"}, created_at=fixed_time, ) client = MagicMock(spec=RedisConnector) mock_get = AsyncMock(return_value=existing_task.model_dump(mode="json")) client.get = mock_get mocker.patch.object(datasources, "get_redis_client", return_value=client) result = await task.get_task(token=token, status="success") assert result is None mock_get.assert_called_once_with(key=task.token_key(token)) async def test_update_task_no_task(mocker: MockerFixture) -> None: """ Test update_task function raises ValueError when the task with the provided token does not exist. """ mock_set = AsyncMock() client = MagicMock(spec=RedisConnector, set=mock_set) mocker.patch.object(datasources, "get_redis_client", return_value=client) mocker.patch("product_staging.models.task.get_task", return_value=None) with pytest.raises( ValueError, match=f"Task with token {str(token)} does not exist." ): await task.update_task(token=token, status="success", payload=payload) async def test_update_task(mocker: MockerFixture) -> None: """ Test update_task function. update_task should call get_task to retrieve the existing task, update its status and payload, and then call set on Redis with the updated Task object serialized to JSON. It should return the updated Task object. """ status: TaskStatus = "success" mock_set = AsyncMock() client = MagicMock(spec=RedisConnector, set=mock_set) existing_task = Task( token=token, status="in_progress", payload={"key": "value"}, created_at=fixed_time, ) mocker.patch.object(datasources, "get_redis_client", return_value=client) mocker.patch("product_staging.models.task.get_task", return_value=existing_task) result = await task.update_task(token=token, status=status, payload=payload) mock_set.assert_called_once_with( key=task.token_key(token), item=Task( token=token, status=status, payload=payload, created_at=fixed_time, ).model_dump(mode="json"), ttl=task.TASK_TTL, ) assert result.token == token assert result.status == status assert result.payload == payload async def test_complete_task(mocker: MockerFixture) -> None: """ Test complete_task function calls update_task with the COMPLETED status and the provided payload. """ mock_update_task = mocker.patch("product_staging.models.task.update_task") await task.complete_task(token=token, payload=payload) mock_update_task.assert_called_once_with( token=token, status="success", payload=payload ) async def test_fail_task(mocker: MockerFixture) -> None: """ Test fail_task function calls update_task with the failure status and the provided payload. """ mock_update_task = mocker.patch("product_staging.models.task.update_task") await task.fail_task(token=token, payload=payload) mock_update_task.assert_called_once_with( token=token, status="failure", payload=payload )