"""Unit tests for src.task_protection.""" import json import urllib.error as urlerr from unittest.mock import MagicMock, patch import pytest from src import task_protection @patch.dict("src.task_protection.os.environ", {}, clear=True) def test_set_task_protection_noop_without_agent_uri() -> None: """No ECS_AGENT_URI → no HTTP call, treated as success.""" with patch("src.task_protection.urllib.request.urlopen") as mock_urlopen: result = task_protection._set_task_protection(MagicMock(), True) assert result is True mock_urlopen.assert_not_called() @patch.dict( "src.task_protection.os.environ", {"ECS_AGENT_URI": "http://agent.local/v4"} ) @pytest.mark.parametrize("enabled", [True, False]) def test_set_task_protection_sends_put(enabled: bool) -> None: """Should PUT JSON body with ProtectionEnabled to the agent endpoint.""" mock_resp = MagicMock() mock_resp.__enter__.return_value = mock_resp mock_resp.__exit__.return_value = False with patch( "src.task_protection.urllib.request.urlopen", return_value=mock_resp ) as mock_urlopen: result = task_protection._set_task_protection(MagicMock(), enabled) assert result is True req = mock_urlopen.call_args[0][0] assert req.full_url == "http://agent.local/v4/task-protection/v1/state" assert req.get_method() == "PUT" assert json.loads(req.data) == {"ProtectionEnabled": enabled} @patch.dict( "src.task_protection.os.environ", {"ECS_AGENT_URI": "http://agent.local/v4"} ) def test_set_task_protection_returns_false_and_logs_on_http_error() -> None: """Network/HTTP failure should be logged at error level and return False.""" log = MagicMock() with patch( "src.task_protection.urllib.request.urlopen", side_effect=urlerr.URLError("boom"), ): result = task_protection._set_task_protection(log, True) assert result is False log.error.assert_called_once() @patch("src.task_protection._set_task_protection") def test_with_task_protection_skips_when_enable_fails( mock_set_protection: MagicMock, ) -> None: """Decorator should skip the wrapped call and log a warning on enable failure.""" mock_set_protection.return_value = False inner = MagicMock() inner.__name__ = "inner" wrapped = task_protection.with_task_protection(inner) log = MagicMock() result = wrapped(log, "arg") assert result is False inner.assert_not_called() log.warning.assert_called_once() # Only the enable attempt; no disable since we never enabled. assert mock_set_protection.call_count == 1 assert mock_set_protection.call_args[0][1] is True @patch("src.task_protection._set_task_protection", return_value=True) def test_with_task_protection_runs_and_disables( mock_set_protection: MagicMock, ) -> None: """Decorator should call the wrapped function and always disable in finally.""" inner = MagicMock() inner.__name__ = "inner" wrapped = task_protection.with_task_protection(inner) log = MagicMock() result = wrapped(log, "arg", kw=1) assert result is True inner.assert_called_once_with(log, "arg", kw=1) toggles = [call[0][1] for call in mock_set_protection.call_args_list] assert toggles == [True, False] @patch("src.task_protection._set_task_protection", return_value=True) def test_with_task_protection_disables_on_exception( mock_set_protection: MagicMock, ) -> None: """Decorator should disable protection even if the wrapped call raises.""" inner = MagicMock(side_effect=RuntimeError("boom")) inner.__name__ = "inner" wrapped = task_protection.with_task_protection(inner) with pytest.raises(RuntimeError): wrapped(MagicMock()) toggles = [call[0][1] for call in mock_set_protection.call_args_list] assert toggles == [True, False]