"""Unit tests for webhook notification utility.""" from unittest.mock import AsyncMock, MagicMock, patch async def test_webhook_not_called_when_url_not_configured(): with patch("agenteval.utils.webhook.get_settings") as mock_settings: mock_settings.return_value.webhook_url = None mock_settings.return_value.webhook_secret = None with patch("agenteval.utils.webhook.httpx.AsyncClient") as MockClient: from agenteval.utils.webhook import send_run_webhook await send_run_webhook("run-1", "completed", {"pass_rate": 1.0}) MockClient.assert_not_called() async def test_webhook_posts_correct_payload(): captured = {} async def fake_post(url, *, json=None, headers=None, **kwargs): captured["url"] = url captured["json"] = json captured["headers"] = headers resp = MagicMock() resp.status_code = 200 return resp with patch("agenteval.utils.webhook.get_settings") as mock_settings: mock_settings.return_value.webhook_url = "http://hook.example.com/notify" mock_settings.return_value.webhook_secret = "secret123" with patch("agenteval.utils.webhook.httpx.AsyncClient") as MockClient: instance = MockClient.return_value.__aenter__.return_value instance.post = AsyncMock(side_effect=fake_post) from agenteval.utils.webhook import send_run_webhook await send_run_webhook("run-42", "completed", {"pass_rate": 0.9}) assert captured["url"] == "http://hook.example.com/notify" assert captured["json"]["run_id"] == "run-42" assert captured["json"]["status"] == "completed" assert captured["json"]["event"] == "run_completed" assert captured["json"]["summary"]["pass_rate"] == 0.9 assert captured["headers"]["X-Webhook-Secret"] == "secret123" assert "/api/reports/run-42" in captured["json"]["report_url"] async def test_webhook_no_secret_header(): captured_headers = {} async def fake_post(url, *, json=None, headers=None, **kwargs): captured_headers.update(headers or {}) resp = MagicMock() resp.status_code = 200 return resp with patch("agenteval.utils.webhook.get_settings") as mock_settings: mock_settings.return_value.webhook_url = "http://hook.example.com/notify" mock_settings.return_value.webhook_secret = None with patch("agenteval.utils.webhook.httpx.AsyncClient") as MockClient: instance = MockClient.return_value.__aenter__.return_value instance.post = AsyncMock(side_effect=fake_post) from agenteval.utils.webhook import send_run_webhook await send_run_webhook("run-1", "completed", {}) assert "X-Webhook-Secret" not in captured_headers async def test_webhook_silently_ignores_errors(): with patch("agenteval.utils.webhook.get_settings") as mock_settings: mock_settings.return_value.webhook_url = "http://unreachable.example.com" mock_settings.return_value.webhook_secret = None with patch("agenteval.utils.webhook.httpx.AsyncClient") as MockClient: instance = MockClient.return_value.__aenter__.return_value instance.post = AsyncMock(side_effect=Exception("connection refused")) from agenteval.utils.webhook import send_run_webhook # Must not raise — errors are silently logged await send_run_webhook("run-1", "failed", {})