From d8fdb02b615b37d503472c09fdcb29091903e7b6 Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 11 Jan 2024 13:18:35 +0000 Subject: [PATCH] Catch error if edited tech-support message has already been reacted to --- ebmbot/bot.py | 22 ++++++++++++++++++-- tests/test_bot.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/ebmbot/bot.py b/ebmbot/bot.py index a33fccfe..12f54d91 100644 --- a/ebmbot/bot.py +++ b/ebmbot/bot.py @@ -7,6 +7,7 @@ from slack_bolt.adapter.socket_mode import SocketModeHandler from slack_bolt.error import BoltUnhandledRequestError from slack_bolt.util.utils import get_boot_message +from slack_sdk.errors import SlackApiError from workspace.techsupport.jobs import get_dates_from_config as get_tech_support_dates @@ -204,6 +205,10 @@ def _repost_to_tech_support(message, say, ack): # Don't repost messages in DMs with the bot if message["channel_type"] in ["channel", "group"]: # Respond with SOS reaction + # If we've already responded, the attempt to react here will raise + # an exception; if this happens, then the user is editing something + # other than the tech-support keyword in the message, and we don't need to + # repost it again. We let the default error handler will deal with it. app.client.reactions_add( channel=message["channel"], timestamp=message["ts"], name="sos" ) @@ -239,15 +244,28 @@ def join_channel(event, ack): @app.error def handle_errors(error, body): + message_text = body["event"].get("message", {}).get("text", "") if isinstance(error, BoltUnhandledRequestError): # Unhandled messages are common (anything that doesn't get matched # by one of the listeners). We don't want to log those. return BoltResponse(status=200, body="Unhandled message") + elif ( + isinstance(error, SlackApiError) + and error.response.data["error"] == "already_reacted" + and "tech-support" in message_text + ): + # If we're already reacted to a tech-support message and the + # user edits the message, we get a slack error, but that's OK + logger.info( + "Already reacted to tech-support message", + message=message_text, + ) + return BoltResponse( + status=200, body="Already reacted to tech-support message" + ) else: # other error patterns channel = body["event"]["channel"] - message_text = body["event"].get("message", {}).get("text", "") - ts = body["event"]["ts"] logger.error("Unexpected error", error=error, body=body) app.client.reactions_add(channel=channel, timestamp=ts, name="x") diff --git a/tests/test_bot.py b/tests/test_bot.py index f557fc3d..fbed8add 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -1,10 +1,11 @@ import json import time from datetime import datetime, timedelta -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest from slack_bolt.request import BoltRequest +from slack_sdk.errors import SlackApiError from slack_sdk.signature import SignatureVerifier from ebmbot import bot, scheduler @@ -522,6 +523,55 @@ def test_unexpected_error(mock_app): ) +def test_already_reacted_to_tech_support_error(mock_app): + # mock an error from a method that's called during the tech-support + # handling to return an "already reacted" SlackApiError + with patch( + "ebmbot.bot.tech_support_out_of_office", + side_effect=SlackApiError( + message="Error", response=Mock(data={"error": "already_reacted"}) + ), + ): + handle_message( + mock_app, + "tech-support help", + channel="channel", + reaction_count=1, + event_type="message", + expected_status=200, + ) + + assert_slack_client_sends_messages( + mock_app.recorder, + messages_kwargs=[], + ) + + +def test_already_reacted_to_non_tech_support_error(mock_app): + # Only already-reacted to tech support messages are ignored; + # another sort of message that raises this error gets + # reported back to slack + with patch( + "ebmbot.bot.handle_namespace_help", + side_effect=SlackApiError( + message="Error", response=Mock(data={"error": "already_reacted"}) + ), + ): + handle_message( + mock_app, "<@U1234> test help", reaction_count=1, expected_status=500 + ) + + assert_slack_client_sends_messages( + mock_app.recorder, + messages_kwargs=[ + { + "channel": "channel", + "text": "Unexpected error: SlackApiError", + } + ], + ) + + def test_new_channel_created(mock_app): # When a channel_created event is received, the bot user joins that channel handle_event(