Skip to content

Commit 8fbd503

Browse files
WilliamBergaminClaude
andauthored
test: characterization tests for payload_utils assistant predicates (#1548)
Co-authored-by: Claude <svc-devxp-claude@slack-corp.com>
1 parent 12c2da5 commit 8fbd503

5 files changed

Lines changed: 289 additions & 4 deletions

File tree

tests/scenario_tests/test_events_assistant.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def build_payload(event: dict) -> dict:
409409
"user_profile": {},
410410
"thread_ts": "1726133698.626339",
411411
"parent_user_id": "W222",
412-
"channel": "D111",
412+
"channel": "C111",
413413
"event_ts": "1726133700.887259",
414414
"channel_type": "channel",
415415
}
@@ -444,7 +444,7 @@ def build_payload(event: dict) -> dict:
444444
"reply_users": ["U222", "W111"],
445445
"is_locked": False,
446446
},
447-
"channel": "D111",
447+
"channel": "C111",
448448
"hidden": True,
449449
"ts": "1726133701.028300",
450450
"event_ts": "1726133701.028300",

tests/scenario_tests/test_events_ignore_self.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def handle_app_mention(say):
100100
"type": "message",
101101
"channel": "C111",
102102
"ts": "1599529504.000400",
103+
"channel_type": "channel",
103104
},
104105
"reaction": "heart_eyes",
105106
"item_user": "W111",

tests/scenario_tests_async/test_events_assistant.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ def build_payload(event: dict) -> dict:
483483
"user_profile": {},
484484
"thread_ts": "1726133698.626339",
485485
"parent_user_id": "W222",
486-
"channel": "D111",
486+
"channel": "C111",
487487
"event_ts": "1726133700.887259",
488488
"channel_type": "channel",
489489
}
@@ -518,7 +518,7 @@ def build_payload(event: dict) -> dict:
518518
"reply_users": ["U222", "W111"],
519519
"is_locked": False,
520520
},
521-
"channel": "D111",
521+
"channel": "C111",
522522
"hidden": True,
523523
"ts": "1726133701.028300",
524524
"event_ts": "1726133701.028300",

tests/scenario_tests_async/test_events_ignore_self.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ async def test_self_events_disabled(self):
8989
"type": "message",
9090
"channel": "C111",
9191
"ts": "1599529504.000400",
92+
"channel_type": "channel",
9293
},
9394
"reaction": "heart_eyes",
9495
"item_user": "W111",
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
from slack_bolt.request.payload_utils import (
2+
is_event,
3+
is_user_message_event_in_assistant_thread,
4+
is_bot_message_event_in_assistant_thread,
5+
is_other_message_sub_event_in_assistant_thread,
6+
is_assistant_event,
7+
is_assistant_thread_started_event,
8+
is_assistant_thread_context_changed_event,
9+
)
10+
from tests.scenario_tests.test_events_assistant import (
11+
build_payload,
12+
thread_started_event_body,
13+
thread_context_changed_event_body,
14+
user_message_event_body,
15+
user_message_event_body_with_assistant_thread,
16+
message_changed_event_body,
17+
channel_user_message_event_body,
18+
channel_message_changed_event_body,
19+
)
20+
from tests.scenario_tests.test_message_bot import (
21+
bot_message_event_payload,
22+
classic_bot_message_event_payload,
23+
)
24+
from tests.scenario_tests.test_message_deleted import event_payload as message_deleted_channel_body
25+
from tests.scenario_tests.test_events_ignore_self import event_body as reaction_added_event_body
26+
from tests.scenario_tests.test_block_actions import body as block_actions_body
27+
28+
file_share_im_message_body = build_payload(
29+
{
30+
"user": "W222",
31+
"type": "message",
32+
"subtype": "file_share",
33+
"ts": "1726133700.887259",
34+
"text": "uploaded a file",
35+
"files": [
36+
{
37+
"id": "F111",
38+
"created": 1726133700,
39+
"name": "test.png",
40+
"title": "test.png",
41+
"mimetype": "image/png",
42+
"filetype": "png",
43+
"user": "W222",
44+
"size": 12345,
45+
"mode": "hosted",
46+
"is_external": False,
47+
"is_public": False,
48+
"url_private": "https://files.slack.com/files-pri/T111-F111/test.png",
49+
"permalink": "https://example.slack.com/files/W222/F111/test.png",
50+
}
51+
],
52+
"upload": True,
53+
"display_as_bot": False,
54+
"thread_ts": "1726133698.626339",
55+
"channel": "D111",
56+
"event_ts": "1726133700.887259",
57+
"channel_type": "im",
58+
}
59+
)
60+
61+
bot_im_thread_message_body = build_payload(
62+
{
63+
"type": "message",
64+
"ts": "1726133700.887259",
65+
"text": "Here is your answer",
66+
"user": "UB111",
67+
"bot_id": "B111",
68+
"app_id": "A222",
69+
"bot_profile": {
70+
"id": "B111",
71+
"deleted": False,
72+
"name": "assistant-app",
73+
"updated": 1726133600,
74+
"app_id": "A222",
75+
"team_id": "T111",
76+
},
77+
"thread_ts": "1726133698.626339",
78+
"channel": "D111",
79+
"event_ts": "1726133700.887259",
80+
"channel_type": "im",
81+
}
82+
)
83+
84+
im_message_no_thread_ts_body = build_payload(
85+
{
86+
"user": "W222",
87+
"type": "message",
88+
"ts": "1726133700.887259",
89+
"text": "A top-level DM, not in a thread",
90+
"channel": "D111",
91+
"event_ts": "1726133700.887259",
92+
"channel_type": "im",
93+
}
94+
)
95+
96+
slash_command_body = {
97+
"token": "verification_token",
98+
"command": "/test",
99+
"text": "hello",
100+
"user_id": "U111",
101+
"user_name": "primary-owner",
102+
"channel_id": "C111",
103+
"channel_name": "test-channel",
104+
"team_id": "T111",
105+
"team_domain": "test-domain",
106+
"api_app_id": "A111",
107+
"is_enterprise_install": "false",
108+
"response_url": "https://hooks.slack.com/commands/T111/111/xxx",
109+
"trigger_id": "111.222.xxx",
110+
}
111+
112+
113+
class TestPayloadUtils:
114+
def test_is_event(self):
115+
positives = {
116+
"thread_started": thread_started_event_body,
117+
"thread_context_changed": thread_context_changed_event_body,
118+
"user_message_im": user_message_event_body,
119+
"user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread,
120+
"message_changed_im": message_changed_event_body,
121+
"channel_user_message": channel_user_message_event_body,
122+
"channel_message_changed": channel_message_changed_event_body,
123+
"bot_message_channel": bot_message_event_payload,
124+
"classic_bot_message_channel": classic_bot_message_event_payload,
125+
"message_deleted_channel": message_deleted_channel_body,
126+
"reaction_added": reaction_added_event_body,
127+
"file_share_im": file_share_im_message_body,
128+
"bot_im_thread": bot_im_thread_message_body,
129+
"im_no_thread_ts": im_message_no_thread_ts_body,
130+
}
131+
negatives = {
132+
"block_actions": block_actions_body,
133+
"slash_command": slash_command_body,
134+
"empty_dict": {},
135+
}
136+
for key, body in positives.items():
137+
assert is_event(body), f"{key} should be recognized as an event"
138+
for key, body in negatives.items():
139+
assert not is_event(body), f"{key} should NOT be recognized as an event"
140+
141+
def test_is_user_message_event_in_assistant_thread(self):
142+
# Requires: is_im_message_event + thread_ts present + bot_id absent
143+
positives = {
144+
"user_message_im": user_message_event_body,
145+
"user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread,
146+
"file_share_im": file_share_im_message_body,
147+
}
148+
negatives = {
149+
"bot_im_thread": bot_im_thread_message_body,
150+
"im_no_thread_ts": im_message_no_thread_ts_body,
151+
"message_changed_im": message_changed_event_body,
152+
"channel_user_message": channel_user_message_event_body,
153+
"channel_message_changed": channel_message_changed_event_body,
154+
"bot_message_channel": bot_message_event_payload,
155+
"thread_started": thread_started_event_body,
156+
"thread_context_changed": thread_context_changed_event_body,
157+
"reaction_added": reaction_added_event_body,
158+
"block_actions": block_actions_body,
159+
}
160+
for key, body in positives.items():
161+
assert is_user_message_event_in_assistant_thread(
162+
body
163+
), f"{key} should pass {is_user_message_event_in_assistant_thread.__name__}"
164+
for key, body in negatives.items():
165+
assert not is_user_message_event_in_assistant_thread(
166+
body
167+
), f"{key} should NOT pass {is_user_message_event_in_assistant_thread.__name__}"
168+
169+
def test_is_bot_message_event_in_assistant_thread(self):
170+
positives = {
171+
"bot_im_thread": bot_im_thread_message_body,
172+
}
173+
negatives = {
174+
"user_message_im": user_message_event_body,
175+
"file_share_im": file_share_im_message_body,
176+
"im_no_thread_ts": im_message_no_thread_ts_body,
177+
"message_changed_im": message_changed_event_body,
178+
"channel_user_message": channel_user_message_event_body,
179+
"bot_message_channel": bot_message_event_payload,
180+
"classic_bot_message_channel": classic_bot_message_event_payload,
181+
"thread_started": thread_started_event_body,
182+
"thread_context_changed": thread_context_changed_event_body,
183+
"reaction_added": reaction_added_event_body,
184+
"block_actions": block_actions_body,
185+
}
186+
for key, body in positives.items():
187+
assert is_bot_message_event_in_assistant_thread(
188+
body
189+
), f"{key} should pass {is_bot_message_event_in_assistant_thread.__name__}"
190+
for key, body in negatives.items():
191+
assert not is_bot_message_event_in_assistant_thread(
192+
body
193+
), f"{key} should NOT pass {is_bot_message_event_in_assistant_thread.__name__}"
194+
195+
def test_is_bot_message_user_message_asymmetry(self):
196+
assert is_user_message_event_in_assistant_thread(file_share_im_message_body)
197+
assert not is_bot_message_event_in_assistant_thread(file_share_im_message_body)
198+
199+
assert is_bot_message_event_in_assistant_thread(bot_im_thread_message_body)
200+
assert not is_user_message_event_in_assistant_thread(bot_im_thread_message_body)
201+
202+
def test_is_other_message_sub_event_in_assistant_thread(self):
203+
positives = {
204+
"message_changed_im": message_changed_event_body,
205+
}
206+
negatives = {
207+
"user_message_im": user_message_event_body,
208+
"bot_im_thread": bot_im_thread_message_body,
209+
"im_no_thread_ts": im_message_no_thread_ts_body,
210+
"channel_message_changed": channel_message_changed_event_body,
211+
"channel_user_message": channel_user_message_event_body,
212+
"message_deleted_channel": message_deleted_channel_body,
213+
"thread_started": thread_started_event_body,
214+
"reaction_added": reaction_added_event_body,
215+
"block_actions": block_actions_body,
216+
}
217+
for key, body in positives.items():
218+
assert is_other_message_sub_event_in_assistant_thread(
219+
body
220+
), f"{key} should pass {is_other_message_sub_event_in_assistant_thread.__name__}"
221+
for key, body in negatives.items():
222+
assert not is_other_message_sub_event_in_assistant_thread(
223+
body
224+
), f"{key} should NOT pass {is_other_message_sub_event_in_assistant_thread.__name__}"
225+
226+
def test_is_assistant_event(self):
227+
positives = {
228+
"thread_started": thread_started_event_body,
229+
"thread_context_changed": thread_context_changed_event_body,
230+
"user_message_im": user_message_event_body,
231+
"user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread,
232+
"file_share_im": file_share_im_message_body,
233+
"bot_im_thread": bot_im_thread_message_body,
234+
}
235+
negatives = {
236+
"message_changed_im": message_changed_event_body,
237+
"im_no_thread_ts": im_message_no_thread_ts_body,
238+
"channel_user_message": channel_user_message_event_body,
239+
"channel_message_changed": channel_message_changed_event_body,
240+
"bot_message_channel": bot_message_event_payload,
241+
"classic_bot_message_channel": classic_bot_message_event_payload,
242+
"message_deleted_channel": message_deleted_channel_body,
243+
"reaction_added": reaction_added_event_body,
244+
"block_actions": block_actions_body,
245+
}
246+
for key, body in positives.items():
247+
assert is_assistant_event(body), f"{key} should pass {is_assistant_event.__name__}"
248+
for key, body in negatives.items():
249+
assert not is_assistant_event(body), f"{key} should NOT pass {is_assistant_event.__name__}"
250+
251+
def test_is_assistant_thread_started_event(self):
252+
assert is_assistant_thread_started_event(thread_started_event_body)
253+
254+
negatives = {
255+
"thread_context_changed": thread_context_changed_event_body,
256+
"user_message_im": user_message_event_body,
257+
"message_changed_im": message_changed_event_body,
258+
"bot_im_thread": bot_im_thread_message_body,
259+
"channel_user_message": channel_user_message_event_body,
260+
"reaction_added": reaction_added_event_body,
261+
"block_actions": block_actions_body,
262+
}
263+
for key, body in negatives.items():
264+
assert not is_assistant_thread_started_event(
265+
body
266+
), f"{key} should NOT pass {is_assistant_thread_started_event.__name__}"
267+
268+
def test_is_assistant_thread_context_changed_event(self):
269+
assert is_assistant_thread_context_changed_event(thread_context_changed_event_body)
270+
271+
negatives = {
272+
"thread_started": thread_started_event_body,
273+
"user_message_im": user_message_event_body,
274+
"message_changed_im": message_changed_event_body,
275+
"bot_im_thread": bot_im_thread_message_body,
276+
"channel_user_message": channel_user_message_event_body,
277+
"reaction_added": reaction_added_event_body,
278+
"block_actions": block_actions_body,
279+
}
280+
for key, body in negatives.items():
281+
assert not is_assistant_thread_context_changed_event(
282+
body
283+
), f"{key} should NOT pass {is_assistant_thread_context_changed_event.__name__}"

0 commit comments

Comments
 (0)