""" Tests for scan_processor task registry and control functions. Tests cancel_scan_task, pause_scan_task, stop_scan_task, and the done-callback that removes tasks from the registry on completion. """ import asyncio import pytest import sys import os from unittest.mock import MagicMock sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from scan_processor import ( _running_tasks, _cancel_reasons, cancel_scan_task, pause_scan_task, stop_scan_task, ) class TestScanProcessorControl: """Tests for task registry and cancel/pause/stop functions.""" def teardown_method(self, _method): """Clean up any test state from _running_tasks and _cancel_reasons.""" for key in [9001, 8001, 8002, 7001]: _running_tasks.pop(key, None) _cancel_reasons.pop(key, None) # ── cancel_scan_task ─────────────────────────────────────────────────── def test_cancel_scan_task_returns_false_when_no_task(self): """Returns False when no task is registered for the given scan id.""" result = cancel_scan_task(99999) assert result is False def test_cancel_scan_task_returns_true_when_task_exists(self): """Returns True and calls task.cancel() when a live task is registered.""" mock_task = MagicMock() mock_task.done.return_value = False _running_tasks[9001] = mock_task result = cancel_scan_task(9001) assert result is True mock_task.cancel.assert_called_once() def test_cancel_scan_task_returns_false_for_completed_task(self): """Returns False when the registered task is already done.""" mock_task = MagicMock() mock_task.done.return_value = True _running_tasks[9001] = mock_task result = cancel_scan_task(9001) assert result is False mock_task.cancel.assert_not_called() # ── pause_scan_task ──────────────────────────────────────────────────── def test_pause_sets_cancel_reason_paused(self): """pause_scan_task sets _cancel_reasons[id] = 'paused'.""" mock_task = MagicMock() mock_task.done.return_value = False _running_tasks[8001] = mock_task pause_scan_task(8001) assert _cancel_reasons.get(8001) == 'paused' def test_pause_calls_cancel_on_task(self): """pause_scan_task triggers cancellation of the underlying task.""" mock_task = MagicMock() mock_task.done.return_value = False _running_tasks[8001] = mock_task result = pause_scan_task(8001) assert result is True mock_task.cancel.assert_called_once() # ── stop_scan_task ───────────────────────────────────────────────────── def test_stop_sets_cancel_reason_cancelled(self): """stop_scan_task sets _cancel_reasons[id] = 'cancelled'.""" mock_task = MagicMock() mock_task.done.return_value = False _running_tasks[8002] = mock_task stop_scan_task(8002) assert _cancel_reasons.get(8002) == 'cancelled' def test_stop_calls_cancel_on_task(self): """stop_scan_task triggers cancellation of the underlying task.""" mock_task = MagicMock() mock_task.done.return_value = False _running_tasks[8002] = mock_task result = stop_scan_task(8002) assert result is True mock_task.cancel.assert_called_once() # ── done callback ────────────────────────────────────────────────────── def test_task_removed_from_registry_on_completion(self): """The done-callback registered by start_scan_processor removes the task.""" async def run(): async def quick(): return task = asyncio.create_task(quick()) _running_tasks[7001] = task task.add_done_callback(lambda _: _running_tasks.pop(7001, None)) await task # Yield to let done callbacks fire await asyncio.sleep(0) return 7001 not in _running_tasks result = asyncio.run(run()) assert result is True