11from time import sleep
2+ from typing import Callable
23
34from slack_sdk .web import WebClient
45
56from slack_bolt import App , BoltRequest , Assistant , Say , SetSuggestedPrompts , SetStatus , BoltContext
7+ from slack_bolt .middleware import Middleware
8+ from slack_bolt .request import BoltRequest as BoltRequestType
9+ from slack_bolt .response import BoltResponse
610from tests .mock_web_api_server import (
711 setup_mock_web_api_server ,
812 cleanup_mock_web_api_server ,
@@ -44,6 +48,7 @@ def assert_target_called():
4448 def start_thread (say : Say , set_suggested_prompts : SetSuggestedPrompts , context : BoltContext ):
4549 assert context .channel_id == "D111"
4650 assert context .thread_ts == "1726133698.626339"
51+ assert say .thread_ts == context .thread_ts
4752 say ("Hi, how can I help you today?" )
4853 set_suggested_prompts (prompts = [{"title" : "What does SLACK stand for?" , "message" : "What does SLACK stand for?" }])
4954 set_suggested_prompts (
@@ -61,6 +66,7 @@ def handle_thread_context_changed(context: BoltContext):
6166 def handle_user_message (say : Say , set_status : SetStatus , context : BoltContext ):
6267 assert context .channel_id == "D111"
6368 assert context .thread_ts == "1726133698.626339"
69+ assert say .thread_ts == context .thread_ts
6470 try :
6571 set_status ("is typing..." )
6672 say ("Here you are!" )
@@ -102,6 +108,86 @@ def handle_user_message(say: Say, set_status: SetStatus, context: BoltContext):
102108 response = app .dispatch (request )
103109 assert response .status == 404
104110
111+ def test_assistant_threads_with_custom_listener_middleware (self ):
112+ app = App (client = self .web_client )
113+ assistant = Assistant ()
114+
115+ state = {"called" : False , "middleware_called" : False }
116+
117+ def assert_target_called ():
118+ count = 0
119+ while state ["called" ] is False and count < 20 :
120+ sleep (0.1 )
121+ count += 1
122+ assert state ["called" ] is True
123+ state ["called" ] = False
124+
125+ class TestMiddleware (Middleware ):
126+ def process (self , * , req : BoltRequestType , resp : BoltResponse , next : Callable [[], BoltResponse ]):
127+ state ["middleware_called" ] = True
128+ # Verify assistant utilities are available
129+ assert req .context .get ("set_status" ) is not None
130+ assert req .context .get ("set_title" ) is not None
131+ assert req .context .get ("set_suggested_prompts" ) is not None
132+ assert req .context .get ("get_thread_context" ) is not None
133+ assert req .context .get ("save_thread_context" ) is not None
134+ return next ()
135+
136+ @assistant .thread_started (middleware = [TestMiddleware ()])
137+ def start_thread (say : Say , set_suggested_prompts : SetSuggestedPrompts , context : BoltContext ):
138+ assert context .channel_id == "D111"
139+ assert context .thread_ts == "1726133698.626339"
140+ assert say .thread_ts == context .thread_ts
141+ say ("Hi, how can I help you today?" )
142+ set_suggested_prompts (prompts = [{"title" : "What does SLACK stand for?" , "message" : "What does SLACK stand for?" }])
143+ state ["called" ] = True
144+
145+ @assistant .user_message (middleware = [TestMiddleware ()])
146+ def handle_user_message (say : Say , set_status : SetStatus , context : BoltContext ):
147+ assert context .channel_id == "D111"
148+ assert context .thread_ts == "1726133698.626339"
149+ assert say .thread_ts == context .thread_ts
150+ set_status ("is typing..." )
151+ say ("Here you are!" )
152+ state ["called" ] = True
153+
154+ app .assistant (assistant )
155+
156+ request = BoltRequest (body = thread_started_event_body , mode = "socket_mode" )
157+ response = app .dispatch (request )
158+ assert response .status == 200
159+ assert_target_called ()
160+ assert state ["middleware_called" ] is True
161+ state ["middleware_called" ] = False
162+
163+ request = BoltRequest (body = user_message_event_body , mode = "socket_mode" )
164+ response = app .dispatch (request )
165+ assert response .status == 200
166+ assert_target_called ()
167+ assert state ["middleware_called" ] is True
168+
169+ def test_assistant_threads_custom_middleware_can_short_circuit (self ):
170+ app = App (client = self .web_client )
171+ assistant = Assistant ()
172+
173+ state = {"handler_called" : False }
174+
175+ class BlockingMiddleware (Middleware ):
176+ def process (self , * , req : BoltRequestType , resp : BoltResponse , next : Callable [[], BoltResponse ]):
177+ # Intentionally not calling next() to short-circuit
178+ return BoltResponse (status = 200 )
179+
180+ @assistant .thread_started (middleware = [BlockingMiddleware ()])
181+ def start_thread (say : Say , context : BoltContext ):
182+ state ["handler_called" ] = True
183+
184+ app .assistant (assistant )
185+
186+ request = BoltRequest (body = thread_started_event_body , mode = "socket_mode" )
187+ response = app .dispatch (request )
188+ assert response .status == 200
189+ assert state ["handler_called" ] is False
190+
105191
106192def build_payload (event : dict ) -> dict :
107193 return {
0 commit comments