diff --git a/slack_server_mock/actor/actor.py b/slack_server_mock/actor/actor.py index 104d838..371471f 100644 --- a/slack_server_mock/actor/actor.py +++ b/slack_server_mock/actor/actor.py @@ -1,6 +1,7 @@ """ Slack Actor """ import datetime import json +from typing import List, Tuple from injector import inject, singleton from tornado.locks import Event @@ -16,6 +17,7 @@ def __init__(self) -> None: self._websocket = None self._event = Event() self._response = None + self._ephemeral_messages = [] def app_connected(self, websocket: WebSocketHandler): """ Notify the actor that the application connected """ @@ -29,28 +31,32 @@ def is_app_connected(self): """ Check if the application is connected """ return self._websocket is not None - async def _wait_for_response(self, timeout: float): + async def _wait_for_response(self, timeout: float) -> Tuple[str, List[str]]: if timeout < 0: return - await self._event.wait( - timeout=(None if timeout == 0 else datetime.timedelta(seconds=timeout)) - ) - response = self._response + try: + await self._event.wait( + timeout=(None if timeout == 0 else datetime.timedelta(seconds=timeout)) + ) + except util.TimeoutError: + # In case of timeout make sure to return ephemeral_messages that were accumulated + pass + + response = self._response or "" self._response = None - return response + ephemeral_messages = self._ephemeral_messages + self._ephemeral_messages = [] + return response, ephemeral_messages - async def send_message(self, msg: str, timeout: float = 300.0) -> str: + async def send_message(self, msg: str, timeout: float = 300.0) -> Tuple[str, List[str]]: """ Send a message to the application """ if not self.is_app_connected(): raise WebSocketClosedError() self._websocket.write_message(self._wrap_message_with_envelope(msg)) - try: - return await self._wait_for_response(timeout=timeout) - except util.TimeoutError: - return "" + return await self._wait_for_response(timeout=timeout) def message_received(self, msg: str): """ Notify the actor that a message was received """ @@ -58,6 +64,10 @@ def message_received(self, msg: str): self._event.set() self._event.clear() + def ephemeral_received(self, msg: str): + """ Notify the actor that an ephemeral message was received """ + self._ephemeral_messages.append(msg) + @staticmethod def _wrap_message_with_envelope(msg: str): return json.dumps( diff --git a/slack_server_mock/servers/actor/handler.py b/slack_server_mock/servers/actor/handler.py index 4cdda54..6679b6b 100644 --- a/slack_server_mock/servers/actor/handler.py +++ b/slack_server_mock/servers/actor/handler.py @@ -28,7 +28,7 @@ async def post(self): self.write({"error": "The application is not connected"}) return - self.write({"answer": response}) + self.write({"answer": response[0], "ephemeral": response[1]}) class ConnectedHandler(RequestHandler): # pylint: disable=W0223 diff --git a/slack_server_mock/servers/http/handler.py b/slack_server_mock/servers/http/handler.py index af6f96b..9910358 100644 --- a/slack_server_mock/servers/http/handler.py +++ b/slack_server_mock/servers/http/handler.py @@ -128,6 +128,29 @@ def post(self): ) +class ChatPostEphemeralHandler(BaseSlackHandler): # pylint: disable=W0223 + """ Handler for chat.postEphemeral endpoint """ + + def post(self): + """ Handle post request """ + # Validate the request + if not self._is_request_valid(): + return + # Get the payload + data = load_json_from_body(self) + if not data: + return + # Notify the actor that a message was received + global_injector.get(Actor).ephemeral_received(data['text']) + # Write back to the application + self.write( + { + "ok": True, + "ts": datetime.timestamp(datetime.now()) + } + ) + + class ConversationsListHandler(BaseSlackHandler): # pylint: disable=W0223 """ Handler for conversations.list endpoint """ def _handle(self): diff --git a/slack_server_mock/servers/http/server.py b/slack_server_mock/servers/http/server.py index 2457b65..b4491d4 100644 --- a/slack_server_mock/servers/http/server.py +++ b/slack_server_mock/servers/http/server.py @@ -20,6 +20,7 @@ def __init__(self, settings: Settings) -> None: (r"/apps.connections.open", handler.AppsConnectionsOpenHandler), (r"/api.test", handler.ApiTestHandler), (r"/chat.postMessage", handler.ChatPostMessageHandler), + (r"/chat.postEphemeral", handler.ChatPostEphemeralHandler), (r"/conversations.join", handler.ConversationsJoinHandler), (r"/conversations.list", handler.ConversationsListHandler), ] diff --git a/test/slackbot.py b/test/slackbot.py index 3377870..07fbbe6 100644 --- a/test/slackbot.py +++ b/test/slackbot.py @@ -27,6 +27,11 @@ def run(self, block=False): def _got_message(self, message, say): print("Got message {}".format(message['text'])) + self._handler.app.client.chat_postEphemeral( + channel=message['channel'], + user=message['user'], + text=message['text'] + ) say(message['text']) def is_connected(self) -> bool: diff --git a/test/test_echo.py b/test/test_echo.py index cb89d97..6a2e0b6 100644 --- a/test/test_echo.py +++ b/test/test_echo.py @@ -54,5 +54,17 @@ def bot(podman_container): def test_echo(bot, podman_container): msg = "foo" + # Send a message res = requests.post(url="http://localhost:8080/message", json={"message": msg}) - assert res.json().get('answer') == msg + + # Assert that the answer exists and that it is an echo of the message + answer = res.json().get('answer') + assert answer is not None + assert answer == msg + + # Assert that the one ephemeral message exists and that it is an echo of the message + ephemeral_messages = res.json().get('ephemeral') + assert ephemeral_messages is not None + assert isinstance(ephemeral_messages, list) + assert len(ephemeral_messages) == 1 + assert ephemeral_messages[0] == msg