Skip to main content

slack_morphism/models/events/
push.rs

1use rsb_derive::Builder;
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4use url::Url;
5
6use crate::blocks::*;
7use crate::events::*;
8use crate::models::messages::*;
9use crate::*;
10
11#[allow(clippy::large_enum_variant)]
12#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
13#[serde(tag = "type")]
14pub enum SlackPushEvent {
15    #[serde(rename = "url_verification")]
16    UrlVerification(SlackUrlVerificationEvent),
17    #[serde(rename = "event_callback")]
18    EventCallback(SlackPushEventCallback),
19    #[serde(rename = "app_rate_limited")]
20    AppRateLimited(SlackAppRateLimitedEvent),
21}
22
23#[skip_serializing_none]
24#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
25pub struct SlackUrlVerificationEvent {
26    pub challenge: String,
27}
28
29#[skip_serializing_none]
30#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
31pub struct SlackAppRateLimitedEvent {
32    pub team_id: String,
33    pub minute_rate_limited: SlackDateTime,
34    pub api_app_id: String,
35}
36
37#[skip_serializing_none]
38#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
39pub struct SlackPushEventCallback {
40    pub team_id: SlackTeamId,
41    pub api_app_id: SlackAppId,
42    pub event: SlackEventCallbackBody,
43    pub event_id: SlackEventId,
44    pub event_time: SlackDateTime,
45    pub event_context: Option<SlackEventContext>,
46    pub authed_users: Option<Vec<SlackUserId>>,
47    pub authorizations: Option<Vec<SlackEventAuthorization>>,
48}
49
50/// A type this crate does not model, or a known type whose payload does not match
51/// the modelled shape, falls through to `Unknown` with the raw JSON preserved.
52#[non_exhaustive]
53#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
54#[serde(tag = "type", rename_all = "snake_case")]
55pub enum SlackEventCallbackBody {
56    Message(SlackMessageEvent),
57    AppHomeOpened(SlackAppHomeOpenedEvent),
58    AppMention(SlackAppMentionEvent),
59    AppUninstalled(SlackAppUninstalledEvent),
60    LinkShared(SlackLinkSharedEvent),
61    EmojiChanged(SlackEmojiChangedEvent),
62    MemberJoinedChannel(SlackMemberJoinedChannelEvent),
63    MemberLeftChannel(SlackMemberLeftChannelEvent),
64    ChannelCreated(SlackChannelCreatedEvent),
65    ChannelDeleted(SlackChannelDeletedEvent),
66    ChannelArchive(SlackChannelArchiveEvent),
67    ChannelRename(SlackChannelRenameEvent),
68    ChannelUnarchive(SlackChannelUnarchiveEvent),
69    TeamJoin(SlackTeamJoinEvent),
70    FileCreated(SlackFileCreatedEvent),
71    FileChange(SlackFileChangedEvent),
72    FileDeleted(SlackFileDeletedEvent),
73    FileShared(SlackFileSharedEvent),
74    FileUnshared(SlackFileUnsharedEvent),
75    FilePublic(SlackFilePublicEvent),
76    ReactionAdded(SlackReactionAddedEvent),
77    ReactionRemoved(SlackReactionRemovedEvent),
78    StarAdded(SlackStarAddedEvent),
79    StarRemoved(SlackStarRemovedEvent),
80    UserChange(SlackUserChangeEvent),
81    UserStatusChanged(SlackUserStatusChangedEvent),
82    AssistantThreadStarted(SlackAssistantThreadStartedEvent),
83    AssistantThreadContextChanged(SlackAssistantThreadContextChangedEvent),
84    AgentSessionStopped(SlackAgentSessionStoppedEvent),
85    AgentSessionTitleChanged(SlackAgentSessionTitleChangedEvent),
86    AppContextChanged(SlackAppContextChangedEvent),
87    /// Any event type not modelled above, carried verbatim so callers can log it.
88    #[serde(untagged)]
89    Unknown(serde_json::Value),
90}
91
92#[skip_serializing_none]
93#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
94pub struct SlackMessageEvent {
95    #[serde(flatten)]
96    pub origin: SlackMessageOrigin,
97    #[serde(flatten)]
98    pub content: Option<SlackMessageContent>,
99    #[serde(flatten)]
100    pub sender: SlackMessageSender,
101    pub subtype: Option<SlackMessageEventType>,
102    pub hidden: Option<bool>,
103    pub message: Option<SlackMessageEventEdited>,
104    pub previous_message: Option<SlackMessageEventEdited>,
105    pub deleted_ts: Option<SlackTs>,
106}
107
108#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
109pub enum SlackMessageEventType {
110    #[serde(rename = "bot_message")]
111    BotMessage,
112    #[serde(rename = "me_message")]
113    MeMessage,
114    #[serde(rename = "channel_join")]
115    ChannelJoin,
116    #[serde(rename = "channel_leave")]
117    ChannelLeave,
118    #[serde(rename = "bot_add")]
119    BotAdd,
120    #[serde(rename = "bot_remove")]
121    BotRemove,
122    #[serde(rename = "channel_topic")]
123    ChannelTopic,
124    #[serde(rename = "channel_purpose")]
125    ChannelPurpose,
126    #[serde(rename = "channel_name")]
127    ChannelName,
128    #[serde(rename = "file_share")]
129    FileShare,
130    #[serde(rename = "message_changed")]
131    MessageChanged,
132    #[serde(rename = "message_deleted")]
133    MessageDeleted,
134    #[serde(rename = "thread_broadcast")]
135    ThreadBroadcast,
136    #[serde(rename = "tombstone")]
137    Tombstone,
138    #[serde(rename = "joiner_notification")]
139    JoinerNotification,
140    #[serde(rename = "slackbot_response")]
141    SlackbotResponse,
142    #[serde(rename = "emoji_changed")]
143    EmojiChanged,
144    #[serde(rename = "sh_room_created")]
145    SlackHuddleRoomCreated,
146    #[serde(rename = "channel_archive")]
147    ChannelArchive,
148    #[serde(rename = "channel_unarchive")]
149    ChannelUnarchive,
150    #[serde(rename = "app_conversation_leave")]
151    AppConversationLeave,
152    #[serde(rename = "bot_enable")]
153    BotEnable,
154    #[serde(rename = "bot_disable")]
155    BotDisable,
156    #[serde(rename = "pinned_item")]
157    PinnedItem,
158    #[serde(rename = "reminder_add")]
159    ReminderAdd,
160    #[serde(rename = "file_comment")]
161    FileComment,
162    #[serde(rename = "file_created")]
163    FileCreated,
164    #[serde(rename = "file_change")]
165    FileChanged,
166    #[serde(rename = "file_deleted")]
167    FileDeleted,
168    #[serde(rename = "file_shared")]
169    FileShared,
170    #[serde(rename = "file_unshared")]
171    FileUnshared,
172    #[serde(rename = "file_public")]
173    FilePublic,
174    #[serde(rename = "huddle_thread")]
175    HuddleThread,
176    #[serde(rename = "share_shortcut")]
177    ShareShortcut,
178    #[serde(rename = "channel_canvas_updated")]
179    ChannelCanvasUpdated,
180    #[serde(rename = "tabbed_canvas_updated")]
181    TabbedCanvasUpdated,
182    #[serde(rename = "channel_posting_permissions")]
183    ChannelPostingPermissions,
184    /// Catch-all for unknown subtypes Slack may add in the future.
185    /// Preserves the original subtype string.
186    #[serde(untagged)]
187    Other(String),
188}
189
190#[skip_serializing_none]
191#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
192pub struct SlackAppHomeOpenedEvent {
193    pub user: SlackUserId,
194    pub channel: SlackChannelId,
195    pub tab: Option<String>,
196    pub view: Option<SlackView>,
197}
198
199#[skip_serializing_none]
200#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
201pub struct SlackAppMentionEvent {
202    pub user: SlackUserId,
203    pub channel: SlackChannelId,
204    #[serde(flatten)]
205    pub content: SlackMessageContent,
206    #[serde(flatten)]
207    pub origin: SlackMessageOrigin,
208    pub edited: Option<SlackMessageEdited>,
209}
210
211#[skip_serializing_none]
212#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
213pub struct SlackMessageEventEdited {
214    #[serde(flatten)]
215    pub content: Option<SlackMessageContent>,
216    #[serde(flatten)]
217    pub sender: SlackMessageSender,
218    pub ts: SlackTs,
219    pub edited: Option<SlackMessageEdited>,
220}
221
222#[skip_serializing_none]
223#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
224pub struct SlackAppUninstalledEvent {}
225
226#[skip_serializing_none]
227#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
228pub struct SlackLinkSharedEvent {
229    pub channel: SlackChannelId,
230    pub event_ts: SlackTs,
231    pub is_bot_user_member: bool,
232    pub links: Vec<SlackLinkObject>,
233    pub message_ts: SlackTs,
234    pub source: Option<String>,
235    pub unfurl_id: Option<SlackUnfurlId>,
236    pub user: SlackUserId,
237}
238
239#[skip_serializing_none]
240#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
241pub struct SlackEmojiChangedEvent {
242    pub subtype: SlackEmojiEventType,
243    pub name: Option<String>,
244    pub names: Option<Vec<String>>,
245    pub old_name: Option<String>,
246    pub new_name: Option<String>,
247    pub value: Option<Url>,
248    pub event_ts: SlackTs,
249}
250
251#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
252pub enum SlackEmojiEventType {
253    #[serde(rename = "remove")]
254    EmojiRemoved,
255    #[serde(rename = "add")]
256    EmojiAdded,
257    #[serde(rename = "rename")]
258    EmojiRenamed,
259}
260
261#[skip_serializing_none]
262#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
263pub struct SlackLinkObject {
264    pub domain: String,
265    pub url: Url,
266}
267
268#[skip_serializing_none]
269#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
270pub struct SlackMemberJoinedChannelEvent {
271    pub user: SlackUserId,
272    pub channel: SlackChannelId,
273    pub channel_type: SlackChannelType,
274    pub team: SlackTeamId,
275    pub inviter: Option<SlackUserId>,
276}
277
278#[skip_serializing_none]
279#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
280pub struct SlackMemberLeftChannelEvent {
281    pub user: SlackUserId,
282    pub channel: SlackChannelId,
283    pub channel_type: SlackChannelType,
284    pub team: SlackTeamId,
285}
286
287#[skip_serializing_none]
288#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
289pub struct SlackChannelCreatedEvent {
290    pub channel: SlackChannelInfo,
291}
292
293#[skip_serializing_none]
294#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
295pub struct SlackChannelDeletedEvent {
296    pub channel: SlackChannelId,
297}
298
299#[skip_serializing_none]
300#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
301pub struct SlackChannelArchiveEvent {
302    pub channel: SlackChannelId,
303    pub user: SlackUserId,
304}
305
306#[skip_serializing_none]
307#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
308pub struct SlackChannelRenameEvent {
309    pub channel: SlackChannelInfo,
310}
311
312#[skip_serializing_none]
313#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
314pub struct SlackChannelUnarchiveEvent {
315    pub channel: SlackChannelId,
316    pub user: SlackUserId,
317}
318
319#[skip_serializing_none]
320#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
321pub struct SlackTeamJoinEvent {
322    pub user: SlackUser,
323}
324
325#[skip_serializing_none]
326#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
327pub struct SlackFileCreatedEvent {
328    pub user_id: SlackUserId,
329    pub file_id: SlackFileId,
330    pub file: SlackFile,
331}
332
333#[skip_serializing_none]
334#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
335pub struct SlackFileChangedEvent {
336    pub file_id: SlackFileId,
337}
338
339#[skip_serializing_none]
340#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
341pub struct SlackFileDeletedEvent {
342    pub file_id: SlackFileId,
343    pub event_ts: SlackTs,
344}
345
346#[skip_serializing_none]
347#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
348pub struct SlackFileSharedEvent {
349    pub channel_id: SlackChannelId,
350    pub file_id: SlackFileId,
351    pub user_id: SlackUserId,
352    pub event_ts: SlackTs,
353}
354
355#[skip_serializing_none]
356#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
357pub struct SlackFileUnsharedEvent {
358    pub file_id: SlackFileId,
359}
360
361#[skip_serializing_none]
362#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
363pub struct SlackFilePublicEvent {
364    pub file_id: SlackFileId,
365}
366
367#[skip_serializing_none]
368#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
369pub struct SlackReactionAddedEvent {
370    pub user: SlackUserId,
371    pub reaction: SlackReactionName,
372    pub item_user: Option<SlackUserId>,
373    pub item: SlackReactionsItem,
374    pub event_ts: SlackTs,
375}
376
377#[skip_serializing_none]
378#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
379pub struct SlackReactionRemovedEvent {
380    pub user: SlackUserId,
381    pub reaction: SlackReactionName,
382    pub item_user: Option<SlackUserId>,
383    pub item: SlackReactionsItem,
384    pub event_ts: SlackTs,
385}
386
387#[skip_serializing_none]
388#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
389pub struct SlackStarAddedEvent {
390    pub user: SlackUserId,
391    pub item: SlackStarsItem,
392    pub event_ts: SlackTs,
393}
394
395#[skip_serializing_none]
396#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
397pub struct SlackStarRemovedEvent {
398    pub user: SlackUserId,
399    pub item: SlackStarsItem,
400    pub event_ts: SlackTs,
401}
402
403#[skip_serializing_none]
404#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
405pub struct SlackUserChangeEvent {
406    pub user: SlackUser,
407    pub event_ts: SlackTs,
408    pub cache_ts: SlackDateTime,
409}
410
411#[skip_serializing_none]
412#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
413pub struct SlackUserStatusChangedEvent {
414    pub user: SlackUser,
415    pub event_ts: SlackTs,
416    pub cache_ts: SlackDateTime,
417}
418
419#[skip_serializing_none]
420#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
421pub struct SlackAssistantThreadStartedEvent {
422    pub event_ts: SlackTs,
423    pub assistant_thread: SlackAssistantThread,
424}
425
426#[skip_serializing_none]
427#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
428pub struct SlackAssistantThreadContextChangedEvent {
429    pub event_ts: SlackTs,
430    pub assistant_thread: SlackAssistantThread,
431}
432
433/// https://docs.slack.dev/reference/events/agent_session_stopped
434#[skip_serializing_none]
435#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
436pub struct SlackAgentSessionStoppedEvent {
437    pub channel: SlackChannelId,
438    pub thread_ts: Option<SlackTs>,
439    pub streaming_message_ts: Option<Vec<SlackTs>>,
440    pub user: Option<SlackUserId>,
441    pub event_ts: Option<SlackTs>,
442}
443
444/// https://docs.slack.dev/reference/events/agent_session_title_changed
445#[skip_serializing_none]
446#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
447pub struct SlackAgentSessionTitleChangedEvent {
448    pub channel: SlackChannelId,
449    pub thread_ts: Option<SlackTs>,
450    pub title: String,
451    pub previous_title: Option<String>,
452    pub user: Option<SlackUserId>,
453    pub team_id: Option<SlackTeamId>,
454    /// Present for org-level installs.
455    pub enterprise_id: Option<SlackEnterpriseId>,
456    pub event_ts: Option<SlackTs>,
457}
458
459/// https://docs.slack.dev/reference/events/app_context_changed
460///
461/// The payload is `{"context": {"entities": [...]}}` or `{"context": {}}`;
462/// there is no `channel`, `user` or `event_ts`.
463#[skip_serializing_none]
464#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
465pub struct SlackAppContextChangedEvent {
466    pub context: SlackAppContext,
467}
468
469#[skip_serializing_none]
470#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
471pub struct SlackAppContext {
472    pub entities: Option<Vec<SlackAppContextEntity>>,
473}
474
475#[skip_serializing_none]
476#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
477pub struct SlackAppContextEntity {
478    /// e.g. `slack#/types/channel_id`
479    #[serde(rename = "type")]
480    pub entity_type: SlackAppContextEntityType,
481    pub value: String,
482    pub team_id: Option<SlackTeamId>,
483}
484
485#[cfg(test)]
486mod test {
487    use super::*;
488
489    #[test]
490    fn test_slack_event_message_change_event() {
491        let payload = include_str!("./fixtures/message_changed.json");
492        let event: SlackPushEventCallback = serde_json::from_str(payload).unwrap();
493        match event.event {
494            SlackEventCallbackBody::Message(SlackMessageEvent {
495                subtype,
496                message,
497                previous_message,
498                ..
499            }) => {
500                assert_eq!(subtype, Some(SlackMessageEventType::MessageChanged));
501                if let Some(message) = message {
502                    assert_eq!(message.sender.user, Some("UXXXXXXXXXX".into()));
503                    assert_eq!(message.sender.bot_id, None);
504                    assert_eq!(message.ts, "1701735043.989889".into());
505                    assert_eq!(
506                        message.edited.map(|edited| edited.ts),
507                        Some("1701743154.000000".into())
508                    );
509                    assert_eq!(
510                        message.content.unwrap().text,
511                        Some("edited message".to_string())
512                    );
513                } else {
514                    panic!("Message is None");
515                }
516                if let Some(previous_message) = previous_message {
517                    assert_eq!(previous_message.sender.user, Some("UXXXXXXXXXX".into()));
518                    assert_eq!(previous_message.sender.bot_id, None);
519                    assert_eq!(previous_message.ts, "1701735043.989889".into());
520                    assert_eq!(
521                        previous_message.edited.map(|edited| edited.ts),
522                        Some("1701742890.000000".into())
523                    );
524                    assert_eq!(
525                        previous_message.content.unwrap().text,
526                        Some("hey!".to_string())
527                    );
528                } else {
529                    panic!("PreviousMessage is None");
530                }
531            }
532            _ => panic!("Unexpected event type"),
533        }
534    }
535
536    #[test]
537    fn test_slack_event_message_changed_by_bot_event() {
538        let payload = include_str!("./fixtures/message_changed_by_bot.json");
539        let event: SlackPushEventCallback = serde_json::from_str(payload).unwrap();
540        match event.event {
541            SlackEventCallbackBody::Message(SlackMessageEvent {
542                subtype,
543                message,
544                previous_message,
545                ..
546            }) => {
547                assert_eq!(subtype, Some(SlackMessageEventType::MessageChanged));
548                if let Some(message) = message {
549                    assert_eq!(message.sender.user, None);
550                    assert_eq!(message.sender.bot_id, Some("BXXXXXXXXXX".into()));
551                    assert_eq!(message.ts, "1701735043.989889".into());
552                    assert_eq!(
553                        message.edited.map(|edited| edited.ts),
554                        Some("1701743154.000000".into())
555                    );
556                    assert_eq!(
557                        message.content.unwrap().text,
558                        Some("edited message".to_string())
559                    );
560                } else {
561                    panic!("Message is None");
562                }
563                if let Some(previous_message) = previous_message {
564                    assert_eq!(previous_message.sender.user, None);
565                    assert_eq!(previous_message.sender.bot_id, Some("BXXXXXXXXXX".into()));
566                    assert_eq!(previous_message.ts, "1701735043.989889".into());
567                    assert_eq!(
568                        previous_message.edited.map(|edited| edited.ts),
569                        Some("1701742890.000000".into())
570                    );
571                    assert_eq!(
572                        previous_message.content.unwrap().text,
573                        Some("hey!".to_string())
574                    );
575                } else {
576                    panic!("PreviousMessage is None");
577                }
578            }
579            _ => panic!("Unexpected event type"),
580        }
581    }
582
583    #[test]
584    fn test_slack_event_agent_session_stopped() {
585        let payload = include_str!("./fixtures/agent_session_stopped.json");
586        let event: SlackPushEventCallback = serde_json::from_str(payload).unwrap();
587        match event.event {
588            SlackEventCallbackBody::AgentSessionStopped(SlackAgentSessionStoppedEvent {
589                channel,
590                thread_ts,
591                streaming_message_ts,
592                ..
593            }) => {
594                assert_eq!(channel, "CXXXXXXXXXX".into());
595                assert_eq!(thread_ts, Some("1757000000.000100".into()));
596                assert_eq!(streaming_message_ts, Some(vec!["1757000001.000200".into()]));
597            }
598            _ => panic!("Unexpected event type"),
599        }
600    }
601
602    #[test]
603    fn test_slack_event_app_context_changed() {
604        let payload = include_str!("./fixtures/app_context_changed.json");
605        let event: SlackPushEventCallback = serde_json::from_str(payload).unwrap();
606        match event.event {
607            SlackEventCallbackBody::AppContextChanged(SlackAppContextChangedEvent { context }) => {
608                assert_eq!(
609                    context.entities,
610                    Some(vec![SlackAppContextEntity {
611                        entity_type: "slack#/types/channel_id".to_string().into(),
612                        value: "C01234ABDCE".into(),
613                        team_id: Some("T0ABCDE6543".into()),
614                    }])
615                );
616            }
617            _ => panic!("Unexpected event type"),
618        }
619
620        let event: SlackEventCallbackBody =
621            serde_json::from_str(r#"{"type":"app_context_changed","context":{}}"#).unwrap();
622        assert_eq!(
623            event,
624            SlackEventCallbackBody::AppContextChanged(SlackAppContextChangedEvent {
625                context: SlackAppContext { entities: None }
626            })
627        );
628    }
629
630    #[test]
631    fn test_slack_event_unknown_type() {
632        let payload = serde_json::json!({"type":"some_future_event","x":1});
633        let event: SlackEventCallbackBody = serde_json::from_value(payload.clone()).unwrap();
634        assert_eq!(event, SlackEventCallbackBody::Unknown(payload.clone()));
635        assert_eq!(serde_json::to_value(&event).unwrap(), payload);
636    }
637
638    #[test]
639    fn test_slack_event_malformed_known_type_is_unknown() {
640        let payload = serde_json::json!({"type":"app_context_changed","context":"nope"});
641        let event: SlackEventCallbackBody = serde_json::from_value(payload.clone()).unwrap();
642        assert_eq!(event, SlackEventCallbackBody::Unknown(payload));
643    }
644
645    #[test]
646    fn test_slack_event_agent_session_title_changed() {
647        let payload = include_str!("./fixtures/agent_session_title_changed.json");
648        let event: SlackPushEventCallback = serde_json::from_str(payload).unwrap();
649        match event.event {
650            SlackEventCallbackBody::AgentSessionTitleChanged(
651                SlackAgentSessionTitleChangedEvent {
652                    channel,
653                    title,
654                    previous_title,
655                    team_id,
656                    ..
657                },
658            ) => {
659                assert_eq!(channel, "C0123ABC456".into());
660                assert_eq!(title, "Bora Bora trip prep".to_string());
661                assert_eq!(previous_title, Some("Scuba diving research".to_string()));
662                assert_eq!(team_id, Some("T0123ABC456".into()));
663            }
664            _ => panic!("Unexpected event type"),
665        }
666    }
667}