From 19badcdfab23327912cc83b903238b804abbc70e Mon Sep 17 00:00:00 2001 From: Philipp Winkler Date: Wed, 8 Jul 2026 13:14:35 +0200 Subject: [PATCH] fix(msteams): treat HTTP 202 from Power Automate webhooks as success Power Automate workflow webhooks accept the payload asynchronously and return 202 Accepted on success, while legacy Office 365 connectors return 200. send() only accepted 200/201, so every successfully delivered notification via a Power Automate webhook was logged as "Error sending to ms teams ... error: Accepted" even though the message arrived in the channel. Add 202 to the accepted status codes and a test covering send() status code handling. Refs #1893 --- .../integrations/msteams/msteams_msg.py | 4 +- tests/test_ms_teams_send.py | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/test_ms_teams_send.py diff --git a/src/robusta/integrations/msteams/msteams_msg.py b/src/robusta/integrations/msteams/msteams_msg.py index 06b8c3076..86770a4cc 100644 --- a/src/robusta/integrations/msteams/msteams_msg.py +++ b/src/robusta/integrations/msteams/msteams_msg.py @@ -184,7 +184,9 @@ def send(self): self._put_text_files_data_up_to_max_limit(complete_card_map) response = requests.post(self.webhook_url, json=complete_card_map) - if response.status_code not in [200, 201]: + # Power Automate workflow webhooks accept the payload asynchronously and return 202, + # while legacy Office 365 connectors return 200. Both indicate success. + if response.status_code not in [200, 201, 202]: logging.error(f"Error sending to ms teams json: {complete_card_map} error: {response.reason}") if response.text and "error" in response.text.lower(): # teams error indication is in the text only :( diff --git a/tests/test_ms_teams_send.py b/tests/test_ms_teams_send.py new file mode 100644 index 000000000..4c7b11955 --- /dev/null +++ b/tests/test_ms_teams_send.py @@ -0,0 +1,40 @@ +from unittest.mock import MagicMock, patch + +import pytest + +from robusta.integrations.msteams.msteams_msg import MsTeamsMsg + + +def _mock_response(status_code: int, reason: str, text: str = ""): + response = MagicMock() + response.status_code = status_code + response.reason = reason + response.text = text + return response + + +@pytest.mark.parametrize( + "status_code, reason, should_log_error", + [ + (200, "OK", False), + (201, "Created", False), + # Power Automate workflow webhooks accept the payload asynchronously and + # return 202 on success. It must not be logged as an error. + (202, "Accepted", False), + (400, "Bad Request", True), + (500, "Internal Server Error", True), + ], +) +def test_ms_teams_send_status_code_logging(status_code, reason, should_log_error): + msg = MsTeamsMsg(webhook_url="http://example.com/webhook", prefer_redirect_to_platform=False) + response = _mock_response(status_code, reason, text="") + + with patch("robusta.integrations.msteams.msteams_msg.requests.post", return_value=response), patch( + "robusta.integrations.msteams.msteams_msg.logging" + ) as mock_logging: + msg.send() + + if should_log_error: + assert mock_logging.error.called, f"expected an error log for status {status_code}" + else: + mock_logging.error.assert_not_called()