Skip to main content

opentalk_roomserver_client/api/
event.rs

1// SPDX-License-Identifier: EUPL-1.2
2// SPDX-FileCopyrightText: OpenTalk Team <mail@opentalk.eu>
3
4use opentalk_roomserver_types::{
5    breakout::{BREAKOUT_MODULE_ID, event::BreakoutEvent},
6    core::{CORE_MODULE_ID, CoreEvent},
7    error::{ERROR_MODULE_ID, SignalingError},
8};
9use opentalk_roomserver_types_automod::{AUTOMOD_MODULE_ID, event::AutomodEvent};
10use opentalk_roomserver_types_chat::{CHAT_MODULE_ID, event::ChatEvent};
11use opentalk_roomserver_types_echo::{ECHO_MODULE_ID, event::EchoEvent};
12use opentalk_roomserver_types_legal_vote::{LEGAL_VOTE_MODULE_ID, event::LegalVoteEvent};
13use opentalk_roomserver_types_meeting_notes::{MEETING_NOTES_MODULE_ID, MeetingNotesEvent};
14use opentalk_roomserver_types_meeting_report::{
15    MEETING_REPORT_MODULE_ID, event::MeetingReportEvent,
16};
17use opentalk_roomserver_types_moderation::{MODERATION_MODULE_ID, event::ModerationEvent};
18use opentalk_roomserver_types_polls::{POLLS_MODULE_ID, event::PollsEvent};
19use opentalk_roomserver_types_raise_hands::{RAISE_HANDS_MODULE_ID, event::RaiseHandsEvent};
20use opentalk_roomserver_types_recording::{RECORDING_MODULE_ID, event::RecordingEvent};
21use opentalk_roomserver_types_subroom_audio::{SUBROOM_AUDIO_MODULE_ID, event::SubroomAudioEvent};
22use opentalk_roomserver_types_timer::{TIMER_MODULE_ID, TimerEvent};
23use opentalk_roomserver_types_training_participation_report::{
24    TRAINING_PARTICIPATION_REPORT_MODULE_ID, TrainingParticipationReportEvent,
25};
26use opentalk_roomserver_types_transcription::{TRANSCRIPTION_MODULE_ID, event::TranscriptionEvent};
27use opentalk_roomserver_types_whiteboard::{WHITEBOARD_MODULE_ID, WhiteboardEvent};
28use opentalk_types_common::{modules::ModuleId, time::Timestamp};
29use serde::{Deserialize, Serialize};
30// reexport events for easier usage
31pub use {
32    opentalk_roomserver_types_e2ee::{E2EE_MODULE_ID, E2eeEvent},
33    opentalk_roomserver_types_excalidraw::{EXCALIDRAW_MODULE_ID, ExcalidrawEvent},
34    opentalk_roomserver_types_livekit::{
35        Credentials, LIVEKIT_MODULE_ID, LiveKitError, LiveKitEvent, LiveKitState,
36    },
37    opentalk_roomserver_types_reaction::{REACTION_MODULE_ID, ReactionEvent},
38    opentalk_roomserver_types_shared_folder::{
39        event::SharedFolder,
40        {SHARED_FOLDER_MODULE_ID, event::SharedFolderEvent},
41    },
42};
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct SignalingEvent {
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub transaction_id: Option<u64>,
48    timestamp: Timestamp,
49    #[serde(flatten)]
50    pub payload: SignalingModuleEvent,
51}
52
53impl SignalingEvent {
54    pub fn namespace(&self) -> ModuleId {
55        self.payload.namespace()
56    }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60#[serde(tag = "namespace", content = "payload", rename_all = "snake_case")]
61pub enum SignalingModuleEvent {
62    Error(SignalingError),
63    Automod(AutomodEvent),
64    Core(CoreEvent),
65    Breakout(BreakoutEvent),
66    Echo(EchoEvent),
67    Chat(ChatEvent),
68
69    #[serde(rename = "livekit")]
70    LiveKit(LiveKitEvent),
71
72    E2ee(E2eeEvent),
73
74    Timer(TimerEvent),
75    Polls(PollsEvent),
76    SharedFolder(SharedFolderEvent),
77    MeetingReport(MeetingReportEvent),
78    Moderation(ModerationEvent),
79    RaiseHands(RaiseHandsEvent),
80    SubroomAudio(SubroomAudioEvent),
81    MeetingNotes(MeetingNotesEvent),
82    Whiteboard(WhiteboardEvent),
83    Excalidraw(ExcalidrawEvent),
84    LegalVote(LegalVoteEvent),
85    TrainingParticipationReport(TrainingParticipationReportEvent),
86    Recording(RecordingEvent),
87    Transcription(TranscriptionEvent),
88    Reaction(ReactionEvent),
89}
90
91impl SignalingModuleEvent {
92    pub fn namespace(&self) -> ModuleId {
93        match self {
94            Self::Error(..) => ERROR_MODULE_ID,
95            Self::Automod(..) => AUTOMOD_MODULE_ID,
96            Self::Core(..) => CORE_MODULE_ID,
97            Self::Breakout(..) => BREAKOUT_MODULE_ID,
98            Self::Echo(..) => ECHO_MODULE_ID,
99            Self::Chat(..) => CHAT_MODULE_ID,
100            Self::LiveKit(..) => LIVEKIT_MODULE_ID,
101            Self::E2ee(..) => E2EE_MODULE_ID,
102            Self::Timer(..) => TIMER_MODULE_ID,
103            Self::Polls(..) => POLLS_MODULE_ID,
104            Self::SharedFolder(..) => SHARED_FOLDER_MODULE_ID,
105            Self::MeetingReport(..) => MEETING_REPORT_MODULE_ID,
106            Self::Moderation(..) => MODERATION_MODULE_ID,
107            Self::RaiseHands(..) => RAISE_HANDS_MODULE_ID,
108            Self::SubroomAudio(..) => SUBROOM_AUDIO_MODULE_ID,
109            Self::MeetingNotes(..) => MEETING_NOTES_MODULE_ID,
110            Self::Whiteboard(..) => WHITEBOARD_MODULE_ID,
111            Self::Excalidraw(..) => EXCALIDRAW_MODULE_ID,
112            Self::LegalVote(..) => LEGAL_VOTE_MODULE_ID,
113            Self::TrainingParticipationReport(..) => TRAINING_PARTICIPATION_REPORT_MODULE_ID,
114            Self::Recording(..) => RECORDING_MODULE_ID,
115            Self::Transcription(..) => TRANSCRIPTION_MODULE_ID,
116            Self::Reaction(..) => REACTION_MODULE_ID,
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use std::collections::{BTreeMap, BTreeSet};
124
125    use insta::assert_snapshot;
126    use opentalk_roomserver_types::{
127        breakout::event::BreakoutEvent, connection_id::ConnectionId, core::CoreEvent,
128        error::SignalingError,
129    };
130    use opentalk_roomserver_types_automod::event::{AutomodEvent, StoppedReason};
131    use opentalk_roomserver_types_chat::event::ChatEvent;
132    use opentalk_roomserver_types_echo::event::EchoEvent;
133    use opentalk_roomserver_types_excalidraw::ExcalidrawEvent;
134    use opentalk_roomserver_types_legal_vote::{
135        LegalVoteEvent,
136        token::Token,
137        vote::{LegalVoteId, VoteOption},
138    };
139    use opentalk_roomserver_types_livekit::LiveKitEvent;
140    use opentalk_roomserver_types_meeting_notes::MeetingNotesEvent;
141    use opentalk_roomserver_types_meeting_report::event::MeetingReportEvent;
142    use opentalk_roomserver_types_moderation::event::ModerationEvent;
143    use opentalk_roomserver_types_polls::{
144        ChoiceId, PollId,
145        command::{Choices, Vote},
146        event::PollsEvent,
147    };
148    use opentalk_roomserver_types_raise_hands::event::RaiseHandsEvent;
149    use opentalk_roomserver_types_reaction::ReactionEvent;
150    use opentalk_roomserver_types_subroom_audio::{
151        WhisperId,
152        event::SubroomAudioEvent,
153        state::{WhisperGroup, WhisperState},
154    };
155    use opentalk_roomserver_types_timer::TimerEvent;
156    use opentalk_roomserver_types_training_participation_report::TrainingParticipationReportEvent;
157    use opentalk_roomserver_types_transcription::{
158        event::TranscriptionEvent, state::TranscriptionStatus,
159    };
160    use opentalk_roomserver_types_whiteboard::WhiteboardEvent;
161    use opentalk_types_common::{assets::AssetId, modules::ModuleId, time::Timestamp};
162    use opentalk_types_signaling::ParticipantId;
163    use serde::Deserialize;
164    use url::Url;
165
166    use super::SignalingModuleEvent;
167    use crate::api::event::SignalingEvent;
168
169    #[derive(Debug, Clone, Deserialize)]
170    pub struct NamespaceOnly {
171        pub namespace: ModuleId,
172    }
173
174    #[test]
175    fn serialize_event_error_internal() {
176        let event = SignalingEvent {
177            transaction_id: None,
178            timestamp: Timestamp::unix_epoch(),
179            payload: SignalingModuleEvent::Error(SignalingError::Internal),
180        };
181        let raw = serde_json::to_string_pretty(&event).unwrap();
182
183        assert_snapshot!(raw, @r#"
184        {
185          "timestamp": "1970-01-01T00:00:00Z",
186          "namespace": "error",
187          "payload": {
188            "error": "internal"
189          }
190        }
191        "#);
192
193        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
194        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
195        assert_eq!(namespace_only.namespace, event.namespace());
196    }
197
198    #[test]
199    fn serialize_event_error_unknown_namespace() {
200        let event = SignalingEvent {
201            transaction_id: None,
202            timestamp: Timestamp::unix_epoch(),
203            payload: SignalingModuleEvent::Error(SignalingError::UnknownNamespace {
204                invalid_namespace: "foo".into(),
205            }),
206        };
207        let raw = serde_json::to_string_pretty(&event).unwrap();
208
209        assert_snapshot!(raw, @r#"
210        {
211          "timestamp": "1970-01-01T00:00:00Z",
212          "namespace": "error",
213          "payload": {
214            "error": "unknown_namespace",
215            "invalid_namespace": "foo"
216          }
217        }
218        "#);
219
220        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
221        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
222        assert_eq!(namespace_only.namespace, event.namespace());
223    }
224
225    #[test]
226    fn serialize_event_automod() {
227        let event = SignalingEvent {
228            transaction_id: None,
229            timestamp: Timestamp::unix_epoch(),
230            payload: SignalingModuleEvent::Automod(AutomodEvent::Stopped(
231                StoppedReason::SessionFinished,
232            )),
233        };
234        let raw = serde_json::to_string_pretty(&event).unwrap();
235
236        assert_snapshot!(raw, @r#"
237        {
238          "timestamp": "1970-01-01T00:00:00Z",
239          "namespace": "automod",
240          "payload": {
241            "message": "stopped",
242            "reason": "session_finished"
243          }
244        }
245        "#);
246    }
247
248    #[test]
249    fn serialize_event_core() {
250        let event = SignalingEvent {
251            transaction_id: None,
252            timestamp: Timestamp::unix_epoch(),
253            payload: SignalingModuleEvent::Core(CoreEvent::ParticipantConnected {
254                participant_id: ParticipantId::from_u128(0x01),
255                connection_id: ConnectionId::from_u128(0x02),
256                peer_data: Default::default(),
257            }),
258        };
259        let raw = serde_json::to_string_pretty(&event).unwrap();
260
261        assert_snapshot!(raw, @r#"
262        {
263          "timestamp": "1970-01-01T00:00:00Z",
264          "namespace": "core",
265          "payload": {
266            "message": "participant_connected",
267            "participant_id": "00000000-0000-0000-0000-000000000001",
268            "connection_id": "00000000-0000-0000-0000-000000000002"
269          }
270        }
271        "#);
272
273        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
274        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
275        assert_eq!(namespace_only.namespace, event.namespace());
276    }
277
278    #[test]
279    fn serialize_event_breakout() {
280        let event = SignalingEvent {
281            transaction_id: None,
282            timestamp: Timestamp::unix_epoch(),
283            payload: SignalingModuleEvent::Breakout(BreakoutEvent::Closed),
284        };
285        let raw = serde_json::to_string_pretty(&event).unwrap();
286
287        assert_snapshot!(raw, @r#"
288        {
289          "timestamp": "1970-01-01T00:00:00Z",
290          "namespace": "breakout",
291          "payload": {
292            "message": "closed"
293          }
294        }
295        "#);
296
297        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
298        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
299        assert_eq!(namespace_only.namespace, event.namespace());
300    }
301
302    #[test]
303    fn serialize_event_chat() {
304        let event = SignalingEvent {
305            transaction_id: None,
306            timestamp: Timestamp::unix_epoch(),
307            payload: SignalingModuleEvent::Chat(ChatEvent::ChatDisabled {
308                issued_by: ParticipantId::from_u128(0x01),
309            }),
310        };
311        let raw = serde_json::to_string_pretty(&event).unwrap();
312
313        assert_snapshot!(raw, @r#"
314        {
315          "timestamp": "1970-01-01T00:00:00Z",
316          "namespace": "chat",
317          "payload": {
318            "message": "chat_disabled",
319            "issued_by": "00000000-0000-0000-0000-000000000001"
320          }
321        }
322        "#);
323
324        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
325        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
326        assert_eq!(namespace_only.namespace, event.namespace());
327    }
328
329    #[test]
330    fn serialize_event_echo() {
331        let event = SignalingEvent {
332            transaction_id: None,
333            timestamp: Timestamp::unix_epoch(),
334            payload: SignalingModuleEvent::Echo(EchoEvent::Pong),
335        };
336        let raw = serde_json::to_string_pretty(&event).unwrap();
337
338        assert_snapshot!(raw, @r#"
339        {
340          "timestamp": "1970-01-01T00:00:00Z",
341          "namespace": "echo",
342          "payload": {
343            "message": "pong"
344          }
345        }
346        "#);
347
348        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
349        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
350        assert_eq!(namespace_only.namespace, event.namespace());
351    }
352
353    #[test]
354    fn serialize_event_livekit() {
355        let event = SignalingEvent {
356            transaction_id: None,
357            timestamp: Timestamp::unix_epoch(),
358            payload: SignalingModuleEvent::LiveKit(LiveKitEvent::ScreenSharePermissionsUpdated {
359                grant: false,
360                participants: BTreeSet::new(),
361            }),
362        };
363        let raw = serde_json::to_string_pretty(&event).unwrap();
364
365        assert_snapshot!(raw, @r#"
366        {
367          "timestamp": "1970-01-01T00:00:00Z",
368          "namespace": "livekit",
369          "payload": {
370            "message": "screen_share_permissions_updated",
371            "grant": false,
372            "participants": []
373          }
374        }
375        "#);
376
377        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
378        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
379        assert_eq!(namespace_only.namespace, event.namespace());
380    }
381
382    #[test]
383    fn serialize_event_timer() {
384        let event = SignalingEvent {
385            transaction_id: None,
386            timestamp: Timestamp::unix_epoch(),
387            payload: SignalingModuleEvent::Timer(TimerEvent::UpdatedReadyStatus {
388                participant_id: ParticipantId::nil(),
389                status: true,
390            }),
391        };
392        let raw = serde_json::to_string_pretty(&event).unwrap();
393
394        assert_snapshot!(raw, @r#"
395        {
396          "timestamp": "1970-01-01T00:00:00Z",
397          "namespace": "timer",
398          "payload": {
399            "message": "updated_ready_status",
400            "participant_id": "00000000-0000-0000-0000-000000000000",
401            "status": true
402          }
403        }
404        "#);
405
406        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
407        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
408        assert_eq!(namespace_only.namespace, event.namespace());
409    }
410
411    #[test]
412    fn serialize_event_polls() {
413        let event = SignalingEvent {
414            transaction_id: None,
415            timestamp: Timestamp::unix_epoch(),
416            payload: SignalingModuleEvent::Polls(PollsEvent::Voted(Vote {
417                poll_id: PollId::nil(),
418                choices: Choices::Single {
419                    choice_id: ChoiceId::from_u32(0),
420                },
421            })),
422        };
423        let raw = serde_json::to_string_pretty(&event).unwrap();
424
425        assert_snapshot!(raw, @r#"
426        {
427          "timestamp": "1970-01-01T00:00:00Z",
428          "namespace": "polls",
429          "payload": {
430            "message": "voted",
431            "poll_id": "00000000-0000-0000-0000-000000000000",
432            "choice_id": 0
433          }
434        }
435        "#);
436
437        // Check that the ModuleId from the actual SignalingModule matches what we serialize.
438        let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
439        assert_eq!(namespace_only.namespace, event.namespace());
440    }
441
442    #[test]
443    fn serialize_event_meeting_report() {
444        let event = SignalingEvent {
445            transaction_id: None,
446            timestamp: Timestamp::unix_epoch(),
447            payload: SignalingModuleEvent::MeetingReport(MeetingReportEvent::PdfAsset {
448                filename: "name".into(),
449                asset_id: AssetId::nil(),
450            }),
451        };
452        let raw = serde_json::to_string_pretty(&event).unwrap();
453
454        assert_snapshot!(raw, @r#"
455        {
456          "timestamp": "1970-01-01T00:00:00Z",
457          "namespace": "meeting_report",
458          "payload": {
459            "message": "pdf_asset",
460            "filename": "name",
461            "asset_id": "00000000-0000-0000-0000-000000000000"
462          }
463        }
464        "#);
465    }
466
467    #[test]
468    fn serialize_event_moderation() {
469        let event = SignalingEvent {
470            transaction_id: None,
471            timestamp: Timestamp::unix_epoch(),
472            payload: SignalingModuleEvent::Moderation(ModerationEvent::Accepted),
473        };
474        let raw = serde_json::to_string_pretty(&event).unwrap();
475
476        assert_snapshot!(raw, @r#"
477        {
478          "timestamp": "1970-01-01T00:00:00Z",
479          "namespace": "moderation",
480          "payload": {
481            "message": "accepted"
482          }
483        }
484        "#);
485    }
486
487    #[test]
488    fn serialize_event_raise_hands() {
489        let event = SignalingEvent {
490            transaction_id: None,
491            timestamp: Timestamp::unix_epoch(),
492            payload: SignalingModuleEvent::RaiseHands(RaiseHandsEvent::HandRaised {
493                participant: ParticipantId::nil(),
494            }),
495        };
496        let raw = serde_json::to_string_pretty(&event).unwrap();
497
498        assert_snapshot!(raw, @r#"
499        {
500          "timestamp": "1970-01-01T00:00:00Z",
501          "namespace": "raise_hands",
502          "payload": {
503            "message": "hand_raised",
504            "participant": "00000000-0000-0000-0000-000000000000"
505          }
506        }
507        "#);
508    }
509
510    #[test]
511    fn serialize_event_subroom_audio() {
512        let group = WhisperGroup {
513            whisper_id: WhisperId::nil(),
514            participants: BTreeMap::from([
515                (ParticipantId::from_u128(0), WhisperState::Creator),
516                (ParticipantId::from_u128(1), WhisperState::Invited),
517            ]),
518        };
519
520        let event = SignalingEvent {
521            transaction_id: None,
522            timestamp: Timestamp::unix_epoch(),
523            payload: SignalingModuleEvent::SubroomAudio(SubroomAudioEvent::WhisperGroupCreated {
524                token: "<jwt-token>".into(),
525                group: group.into(),
526            }),
527        };
528        let raw = serde_json::to_string_pretty(&event).unwrap();
529
530        assert_snapshot!(raw, @r#"
531        {
532          "timestamp": "1970-01-01T00:00:00Z",
533          "namespace": "subroom_audio",
534          "payload": {
535            "message": "whisper_group_created",
536            "token": "<jwt-token>",
537            "whisper_id": "00000000-0000-0000-0000-000000000000",
538            "participants": [
539              {
540                "participant_id": "00000000-0000-0000-0000-000000000000",
541                "state": "creator"
542              },
543              {
544                "participant_id": "00000000-0000-0000-0000-000000000001",
545                "state": "invited"
546              }
547            ]
548          }
549        }
550        "#);
551    }
552
553    #[test]
554    fn serialize_event_meeting_notes() {
555        let event = SignalingEvent {
556            transaction_id: None,
557            timestamp: Timestamp::unix_epoch(),
558            payload: SignalingModuleEvent::MeetingNotes(MeetingNotesEvent::AccessChanged {
559                readers: Vec::new(),
560                writers: Vec::new(),
561            }),
562        };
563        let raw = serde_json::to_string_pretty(&event).unwrap();
564
565        assert_snapshot!(raw, @r#"
566        {
567          "timestamp": "1970-01-01T00:00:00Z",
568          "namespace": "meeting_notes",
569          "payload": {
570            "message": "access_changed",
571            "readers": [],
572            "writers": []
573          }
574        }
575        "#);
576    }
577
578    #[test]
579    fn serialize_event_whiteboard() {
580        let event = SignalingEvent {
581            transaction_id: None,
582            timestamp: Timestamp::unix_epoch(),
583            payload: SignalingModuleEvent::Whiteboard(WhiteboardEvent::Initialized {
584                url: Url::parse("https://example.com").unwrap(),
585            }),
586        };
587        let raw = serde_json::to_string_pretty(&event).unwrap();
588
589        assert_snapshot!(raw, @r#"
590        {
591          "timestamp": "1970-01-01T00:00:00Z",
592          "namespace": "whiteboard",
593          "payload": {
594            "message": "initialized",
595            "url": "https://example.com/"
596          }
597        }
598        "#);
599    }
600
601    #[test]
602    fn serialize_event_excalidraw() {
603        let event = SignalingEvent {
604            transaction_id: None,
605            timestamp: Timestamp::unix_epoch(),
606            payload: SignalingModuleEvent::Excalidraw(ExcalidrawEvent::Followed {
607                participant_id: ParticipantId::nil(),
608            }),
609        };
610        let raw = serde_json::to_string_pretty(&event).unwrap();
611
612        assert_snapshot!(raw, @r#"
613        {
614          "timestamp": "1970-01-01T00:00:00Z",
615          "namespace": "excalidraw",
616          "payload": {
617            "message": "followed",
618            "participant_id": "00000000-0000-0000-0000-000000000000"
619          }
620        }
621        "#);
622    }
623
624    #[test]
625    fn serialize_event_legal_vote() {
626        let event = SignalingEvent {
627            transaction_id: None,
628            timestamp: Timestamp::unix_epoch(),
629            payload: SignalingModuleEvent::LegalVote(LegalVoteEvent::Voted {
630                legal_vote_id: LegalVoteId::from_u128(1),
631                vote_option: VoteOption::Yes,
632                issuer: ParticipantId::from_u128(2),
633                consumed_token: Token::new(3),
634            }),
635        };
636        let raw = serde_json::to_string_pretty(&event).unwrap();
637
638        assert_snapshot!(raw, @r#"
639        {
640          "timestamp": "1970-01-01T00:00:00Z",
641          "namespace": "legal_vote",
642          "payload": {
643            "message": "voted",
644            "legal_vote_id": "00000000-0000-0000-0000-000000000001",
645            "vote_option": "yes",
646            "issuer": "00000000-0000-0000-0000-000000000002",
647            "consumed_token": "11111111114"
648          }
649        }
650        "#);
651    }
652
653    #[test]
654    fn serialize_training_participation_report() {
655        let event = SignalingEvent {
656            transaction_id: None,
657            timestamp: Timestamp::unix_epoch(),
658            payload: SignalingModuleEvent::TrainingParticipationReport(
659                TrainingParticipationReportEvent::PresenceConfirmationLogged,
660            ),
661        };
662        let raw = serde_json::to_string_pretty(&event).unwrap();
663
664        assert_snapshot!(raw, @r#"
665        {
666          "timestamp": "1970-01-01T00:00:00Z",
667          "namespace": "training_participation_report",
668          "payload": {
669            "message": "presence_confirmation_logged"
670          }
671        }
672        "#);
673    }
674
675    #[test]
676    fn serialize_event_transcription() {
677        let event = SignalingEvent {
678            transaction_id: None,
679            timestamp: Timestamp::unix_epoch(),
680            payload: SignalingModuleEvent::Transcription(TranscriptionEvent::StateUpdated {
681                status: TranscriptionStatus::Running,
682            }),
683        };
684
685        let raw = serde_json::to_string_pretty(&event).unwrap();
686
687        assert_snapshot!(raw, @r#"
688        {
689          "timestamp": "1970-01-01T00:00:00Z",
690          "namespace": "transcription",
691          "payload": {
692            "message": "state_updated",
693            "status": "running"
694          }
695        }
696        "#);
697    }
698
699    #[test]
700    fn serialize_event_reaction() {
701        let event = SignalingEvent {
702            transaction_id: None,
703            timestamp: Timestamp::unix_epoch(),
704            payload: SignalingModuleEvent::Reaction(ReactionEvent::Reacted {
705                participant_id: ParticipantId::nil(),
706                reaction: opentalk_roomserver_types_reaction::Reaction::ThumbsUp,
707            }),
708        };
709
710        let raw = serde_json::to_string_pretty(&event).unwrap();
711
712        assert_snapshot!(raw, @r#"
713        {
714          "timestamp": "1970-01-01T00:00:00Z",
715          "namespace": "reaction",
716          "payload": {
717            "message": "reacted",
718            "participant_id": "00000000-0000-0000-0000-000000000000",
719            "reaction": "thumbs_up"
720          }
721        }
722        "#);
723    }
724}