Skip to main content

pjson_rs/application/dto/
event_dto.rs

1//! Event Data Transfer Objects for serialization
2//!
3//! Handles serialization/deserialization of domain events while keeping
4//! the domain layer clean of serialization concerns.
5
6use crate::{
7    application::dto::{
8        id_dto::{SessionIdDto, StreamIdDto},
9        priority_dto::{FromDto, ToDto},
10    },
11    domain::{
12        DomainError,
13        events::{DomainEvent, EventId, PerformanceMetrics},
14        value_objects::{SessionId, StreamId},
15    },
16};
17use chrono::{DateTime, Utc};
18use serde::{Deserialize, Serialize};
19
20/// Serializable representation of domain events
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22#[serde(tag = "event_type", rename_all = "snake_case")]
23pub enum DomainEventDto {
24    /// Session was activated and is ready to accept streams
25    SessionActivated {
26        session_id: SessionIdDto,
27        timestamp: DateTime<Utc>,
28    },
29
30    /// Session was closed gracefully
31    SessionClosed {
32        session_id: SessionIdDto,
33        timestamp: DateTime<Utc>,
34    },
35
36    /// Session expired due to timeout
37    SessionExpired {
38        session_id: SessionIdDto,
39        timestamp: DateTime<Utc>,
40    },
41
42    /// Session was forcefully closed due to timeout
43    SessionTimedOut {
44        session_id: SessionIdDto,
45        original_state: String, // SessionState serialized as string
46        timeout_duration: u64,
47        timestamp: DateTime<Utc>,
48    },
49
50    /// Session timeout was extended
51    SessionTimeoutExtended {
52        session_id: SessionIdDto,
53        additional_seconds: u64,
54        new_expires_at: DateTime<Utc>,
55        timestamp: DateTime<Utc>,
56    },
57
58    /// New stream was created in the session
59    StreamCreated {
60        session_id: SessionIdDto,
61        stream_id: StreamIdDto,
62        timestamp: DateTime<Utc>,
63    },
64
65    /// Stream started sending data
66    StreamStarted {
67        session_id: SessionIdDto,
68        stream_id: StreamIdDto,
69        timestamp: DateTime<Utc>,
70    },
71
72    /// Stream completed successfully
73    StreamCompleted {
74        session_id: SessionIdDto,
75        stream_id: StreamIdDto,
76        timestamp: DateTime<Utc>,
77    },
78
79    /// Stream failed with error
80    StreamFailed {
81        session_id: SessionIdDto,
82        stream_id: StreamIdDto,
83        error: String,
84        timestamp: DateTime<Utc>,
85    },
86
87    /// Stream was cancelled
88    StreamCancelled {
89        session_id: SessionIdDto,
90        stream_id: StreamIdDto,
91        timestamp: DateTime<Utc>,
92    },
93
94    /// Skeleton frame was generated for a stream
95    SkeletonGenerated {
96        session_id: SessionIdDto,
97        stream_id: StreamIdDto,
98        frame_size_bytes: u64,
99        timestamp: DateTime<Utc>,
100    },
101
102    /// Patch frames were generated for a stream
103    PatchFramesGenerated {
104        session_id: SessionIdDto,
105        stream_id: StreamIdDto,
106        frame_count: usize,
107        total_bytes: u64,
108        highest_priority: u8,
109        timestamp: DateTime<Utc>,
110    },
111
112    /// Multiple frames were batched for efficient sending
113    FramesBatched {
114        session_id: SessionIdDto,
115        frame_count: usize,
116        timestamp: DateTime<Utc>,
117    },
118
119    /// Priority threshold was adjusted for adaptive streaming
120    PriorityThresholdAdjusted {
121        session_id: SessionIdDto,
122        old_threshold: u8,
123        new_threshold: u8,
124        reason: String,
125        timestamp: DateTime<Utc>,
126    },
127
128    /// Stream configuration was updated
129    StreamConfigUpdated {
130        session_id: SessionIdDto,
131        stream_id: StreamIdDto,
132        timestamp: DateTime<Utc>,
133    },
134
135    /// Performance metrics were recorded
136    PerformanceMetricsRecorded {
137        session_id: SessionIdDto,
138        metrics: PerformanceMetricsDto,
139        timestamp: DateTime<Utc>,
140    },
141
142    /// Backpressure signal received from client
143    BackpressureReceived {
144        session_id: SessionIdDto,
145        stream_id: Option<StreamIdDto>,
146        signal: String,
147        timestamp: DateTime<Utc>,
148    },
149
150    /// Stream was paused due to flow control
151    StreamPaused {
152        session_id: SessionIdDto,
153        stream_id: StreamIdDto,
154        reason: String,
155        timestamp: DateTime<Utc>,
156    },
157
158    /// Stream was resumed after pause
159    StreamResumed {
160        session_id: SessionIdDto,
161        stream_id: StreamIdDto,
162        paused_duration_ms: u64,
163        timestamp: DateTime<Utc>,
164    },
165
166    /// Flow control credits were updated
167    CreditsUpdated {
168        session_id: SessionIdDto,
169        available_credits: usize,
170        max_credits: usize,
171        timestamp: DateTime<Utc>,
172    },
173}
174
175/// Serializable representation of performance metrics
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177pub struct PerformanceMetricsDto {
178    pub frames_per_second: f64,
179    pub bytes_per_second: f64,
180    pub average_frame_size: f64,
181    pub priority_distribution: PriorityDistributionDto,
182    pub latency_ms: Option<u64>,
183}
184
185/// Serializable representation of priority distribution
186/// This is just an alias to the main PriorityDistribution from domain events
187pub type PriorityDistributionDto = crate::domain::events::PriorityDistribution;
188
189/// Serializable representation of event ID
190#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
191pub struct EventIdDto {
192    uuid: uuid::Uuid,
193}
194
195impl EventIdDto {
196    /// Create from UUID
197    pub fn new(uuid: uuid::Uuid) -> Self {
198        Self { uuid }
199    }
200
201    /// Get UUID value
202    pub fn uuid(self) -> uuid::Uuid {
203        self.uuid
204    }
205
206    /// Generate new unique event ID
207    pub fn generate() -> Self {
208        Self {
209            uuid: uuid::Uuid::new_v4(),
210        }
211    }
212}
213
214impl std::fmt::Display for EventIdDto {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        write!(f, "{}", self.uuid)
217    }
218}
219
220// Conversion implementations
221
222impl From<DomainEvent> for DomainEventDto {
223    fn from(event: DomainEvent) -> Self {
224        match event {
225            DomainEvent::SessionActivated {
226                session_id,
227                timestamp,
228            } => Self::SessionActivated {
229                session_id: session_id.to_dto(),
230                timestamp,
231            },
232            DomainEvent::SessionClosed {
233                session_id,
234                timestamp,
235            } => Self::SessionClosed {
236                session_id: session_id.to_dto(),
237                timestamp,
238            },
239            DomainEvent::SessionExpired {
240                session_id,
241                timestamp,
242            } => Self::SessionExpired {
243                session_id: session_id.to_dto(),
244                timestamp,
245            },
246            DomainEvent::StreamCreated {
247                session_id,
248                stream_id,
249                timestamp,
250            } => Self::StreamCreated {
251                session_id: session_id.to_dto(),
252                stream_id: stream_id.to_dto(),
253                timestamp,
254            },
255            DomainEvent::StreamStarted {
256                session_id,
257                stream_id,
258                timestamp,
259            } => Self::StreamStarted {
260                session_id: session_id.to_dto(),
261                stream_id: stream_id.to_dto(),
262                timestamp,
263            },
264            DomainEvent::StreamCompleted {
265                session_id,
266                stream_id,
267                timestamp,
268            } => Self::StreamCompleted {
269                session_id: session_id.to_dto(),
270                stream_id: stream_id.to_dto(),
271                timestamp,
272            },
273            DomainEvent::StreamFailed {
274                session_id,
275                stream_id,
276                error,
277                timestamp,
278            } => Self::StreamFailed {
279                session_id: session_id.to_dto(),
280                stream_id: stream_id.to_dto(),
281                error,
282                timestamp,
283            },
284            DomainEvent::StreamCancelled {
285                session_id,
286                stream_id,
287                timestamp,
288            } => Self::StreamCancelled {
289                session_id: session_id.to_dto(),
290                stream_id: stream_id.to_dto(),
291                timestamp,
292            },
293            DomainEvent::SkeletonGenerated {
294                session_id,
295                stream_id,
296                frame_size_bytes,
297                timestamp,
298            } => Self::SkeletonGenerated {
299                session_id: session_id.to_dto(),
300                stream_id: stream_id.to_dto(),
301                frame_size_bytes,
302                timestamp,
303            },
304            DomainEvent::PatchFramesGenerated {
305                session_id,
306                stream_id,
307                frame_count,
308                total_bytes,
309                highest_priority,
310                timestamp,
311            } => Self::PatchFramesGenerated {
312                session_id: session_id.to_dto(),
313                stream_id: stream_id.to_dto(),
314                frame_count,
315                total_bytes,
316                highest_priority,
317                timestamp,
318            },
319            DomainEvent::FramesBatched {
320                session_id,
321                frame_count,
322                timestamp,
323            } => Self::FramesBatched {
324                session_id: session_id.to_dto(),
325                frame_count,
326                timestamp,
327            },
328            DomainEvent::PriorityThresholdAdjusted {
329                session_id,
330                old_threshold,
331                new_threshold,
332                reason,
333                timestamp,
334            } => Self::PriorityThresholdAdjusted {
335                session_id: session_id.to_dto(),
336                old_threshold,
337                new_threshold,
338                reason,
339                timestamp,
340            },
341            DomainEvent::StreamConfigUpdated {
342                session_id,
343                stream_id,
344                timestamp,
345            } => Self::StreamConfigUpdated {
346                session_id: session_id.to_dto(),
347                stream_id: stream_id.to_dto(),
348                timestamp,
349            },
350            DomainEvent::PerformanceMetricsRecorded {
351                session_id,
352                metrics,
353                timestamp,
354            } => Self::PerformanceMetricsRecorded {
355                session_id: session_id.to_dto(),
356                metrics: metrics.into(),
357                timestamp,
358            },
359            DomainEvent::SessionTimedOut {
360                session_id,
361                original_state,
362                timeout_duration,
363                timestamp,
364            } => Self::SessionTimedOut {
365                session_id: session_id.to_dto(),
366                original_state: format!("{:?}", original_state),
367                timeout_duration,
368                timestamp,
369            },
370            DomainEvent::SessionTimeoutExtended {
371                session_id,
372                additional_seconds,
373                new_expires_at,
374                timestamp,
375            } => Self::SessionTimeoutExtended {
376                session_id: session_id.to_dto(),
377                additional_seconds,
378                new_expires_at,
379                timestamp,
380            },
381            DomainEvent::BackpressureReceived {
382                session_id,
383                stream_id,
384                signal,
385                timestamp,
386            } => Self::BackpressureReceived {
387                session_id: session_id.to_dto(),
388                stream_id: stream_id.map(|id| id.to_dto()),
389                signal: format!("{:?}", signal),
390                timestamp,
391            },
392            DomainEvent::StreamPaused {
393                session_id,
394                stream_id,
395                reason,
396                timestamp,
397            } => Self::StreamPaused {
398                session_id: session_id.to_dto(),
399                stream_id: stream_id.to_dto(),
400                reason,
401                timestamp,
402            },
403            DomainEvent::StreamResumed {
404                session_id,
405                stream_id,
406                paused_duration_ms,
407                timestamp,
408            } => Self::StreamResumed {
409                session_id: session_id.to_dto(),
410                stream_id: stream_id.to_dto(),
411                paused_duration_ms,
412                timestamp,
413            },
414            DomainEvent::CreditsUpdated {
415                session_id,
416                available_credits,
417                max_credits,
418                timestamp,
419            } => Self::CreditsUpdated {
420                session_id: session_id.to_dto(),
421                available_credits,
422                max_credits,
423                timestamp,
424            },
425        }
426    }
427}
428
429impl ToDto<DomainEventDto> for DomainEvent {
430    fn to_dto(self) -> DomainEventDto {
431        DomainEventDto::from(self)
432    }
433}
434
435impl FromDto<DomainEventDto> for DomainEvent {
436    type Error = DomainError;
437
438    fn from_dto(dto: DomainEventDto) -> Result<Self, Self::Error> {
439        match dto {
440            DomainEventDto::SessionActivated {
441                session_id,
442                timestamp,
443            } => Ok(Self::SessionActivated {
444                session_id: SessionId::from_dto(session_id)?,
445                timestamp,
446            }),
447            DomainEventDto::SessionClosed {
448                session_id,
449                timestamp,
450            } => Ok(Self::SessionClosed {
451                session_id: SessionId::from_dto(session_id)?,
452                timestamp,
453            }),
454            DomainEventDto::SessionExpired {
455                session_id,
456                timestamp,
457            } => Ok(Self::SessionExpired {
458                session_id: SessionId::from_dto(session_id)?,
459                timestamp,
460            }),
461            DomainEventDto::StreamCreated {
462                session_id,
463                stream_id,
464                timestamp,
465            } => Ok(Self::StreamCreated {
466                session_id: SessionId::from_dto(session_id)?,
467                stream_id: StreamId::from_dto(stream_id)?,
468                timestamp,
469            }),
470            DomainEventDto::StreamStarted {
471                session_id,
472                stream_id,
473                timestamp,
474            } => Ok(Self::StreamStarted {
475                session_id: SessionId::from_dto(session_id)?,
476                stream_id: StreamId::from_dto(stream_id)?,
477                timestamp,
478            }),
479            DomainEventDto::StreamCompleted {
480                session_id,
481                stream_id,
482                timestamp,
483            } => Ok(Self::StreamCompleted {
484                session_id: SessionId::from_dto(session_id)?,
485                stream_id: StreamId::from_dto(stream_id)?,
486                timestamp,
487            }),
488            DomainEventDto::StreamFailed {
489                session_id,
490                stream_id,
491                error,
492                timestamp,
493            } => Ok(Self::StreamFailed {
494                session_id: SessionId::from_dto(session_id)?,
495                stream_id: StreamId::from_dto(stream_id)?,
496                error,
497                timestamp,
498            }),
499            DomainEventDto::StreamCancelled {
500                session_id,
501                stream_id,
502                timestamp,
503            } => Ok(Self::StreamCancelled {
504                session_id: SessionId::from_dto(session_id)?,
505                stream_id: StreamId::from_dto(stream_id)?,
506                timestamp,
507            }),
508            DomainEventDto::SkeletonGenerated {
509                session_id,
510                stream_id,
511                frame_size_bytes,
512                timestamp,
513            } => Ok(Self::SkeletonGenerated {
514                session_id: SessionId::from_dto(session_id)?,
515                stream_id: StreamId::from_dto(stream_id)?,
516                frame_size_bytes,
517                timestamp,
518            }),
519            DomainEventDto::PatchFramesGenerated {
520                session_id,
521                stream_id,
522                frame_count,
523                total_bytes,
524                highest_priority,
525                timestamp,
526            } => Ok(Self::PatchFramesGenerated {
527                session_id: SessionId::from_dto(session_id)?,
528                stream_id: StreamId::from_dto(stream_id)?,
529                frame_count,
530                total_bytes,
531                highest_priority,
532                timestamp,
533            }),
534            DomainEventDto::FramesBatched {
535                session_id,
536                frame_count,
537                timestamp,
538            } => Ok(Self::FramesBatched {
539                session_id: SessionId::from_dto(session_id)?,
540                frame_count,
541                timestamp,
542            }),
543            DomainEventDto::PriorityThresholdAdjusted {
544                session_id,
545                old_threshold,
546                new_threshold,
547                reason,
548                timestamp,
549            } => Ok(Self::PriorityThresholdAdjusted {
550                session_id: SessionId::from_dto(session_id)?,
551                old_threshold,
552                new_threshold,
553                reason,
554                timestamp,
555            }),
556            DomainEventDto::StreamConfigUpdated {
557                session_id,
558                stream_id,
559                timestamp,
560            } => Ok(Self::StreamConfigUpdated {
561                session_id: SessionId::from_dto(session_id)?,
562                stream_id: StreamId::from_dto(stream_id)?,
563                timestamp,
564            }),
565            DomainEventDto::PerformanceMetricsRecorded {
566                session_id,
567                metrics,
568                timestamp,
569            } => Ok(Self::PerformanceMetricsRecorded {
570                session_id: SessionId::from_dto(session_id)?,
571                metrics: metrics.try_into().map_err(|_| {
572                    DomainError::InvalidInput("Invalid performance metrics".to_string())
573                })?,
574                timestamp,
575            }),
576            DomainEventDto::SessionTimedOut {
577                session_id,
578                original_state,
579                timeout_duration,
580                timestamp,
581            } => {
582                // Parse SessionState from string - basic implementation
583                let state = match original_state.as_str() {
584                    "Initializing" => crate::domain::events::SessionState::Initializing,
585                    "Active" => crate::domain::events::SessionState::Active,
586                    "Closing" => crate::domain::events::SessionState::Closing,
587                    "Completed" => crate::domain::events::SessionState::Completed,
588                    "Failed" => crate::domain::events::SessionState::Failed,
589                    _ => {
590                        return Err(DomainError::InvalidInput(format!(
591                            "Invalid session state: {}",
592                            original_state
593                        )));
594                    }
595                };
596                Ok(Self::SessionTimedOut {
597                    session_id: SessionId::from_dto(session_id)?,
598                    original_state: state,
599                    timeout_duration,
600                    timestamp,
601                })
602            }
603            DomainEventDto::SessionTimeoutExtended {
604                session_id,
605                additional_seconds,
606                new_expires_at,
607                timestamp,
608            } => Ok(Self::SessionTimeoutExtended {
609                session_id: SessionId::from_dto(session_id)?,
610                additional_seconds,
611                new_expires_at,
612                timestamp,
613            }),
614            DomainEventDto::BackpressureReceived {
615                session_id,
616                stream_id,
617                signal,
618                timestamp,
619            } => {
620                let backpressure_signal = match signal.as_str() {
621                    "Ok" => crate::domain::value_objects::BackpressureSignal::Ok,
622                    "SlowDown" => crate::domain::value_objects::BackpressureSignal::SlowDown,
623                    "Pause" => crate::domain::value_objects::BackpressureSignal::Pause,
624                    _ => {
625                        return Err(DomainError::InvalidInput(format!(
626                            "Invalid backpressure signal: {}",
627                            signal
628                        )));
629                    }
630                };
631                Ok(Self::BackpressureReceived {
632                    session_id: SessionId::from_dto(session_id)?,
633                    stream_id: stream_id.map(StreamId::from_dto).transpose()?,
634                    signal: backpressure_signal,
635                    timestamp,
636                })
637            }
638            DomainEventDto::StreamPaused {
639                session_id,
640                stream_id,
641                reason,
642                timestamp,
643            } => Ok(Self::StreamPaused {
644                session_id: SessionId::from_dto(session_id)?,
645                stream_id: StreamId::from_dto(stream_id)?,
646                reason,
647                timestamp,
648            }),
649            DomainEventDto::StreamResumed {
650                session_id,
651                stream_id,
652                paused_duration_ms,
653                timestamp,
654            } => Ok(Self::StreamResumed {
655                session_id: SessionId::from_dto(session_id)?,
656                stream_id: StreamId::from_dto(stream_id)?,
657                paused_duration_ms,
658                timestamp,
659            }),
660            DomainEventDto::CreditsUpdated {
661                session_id,
662                available_credits,
663                max_credits,
664                timestamp,
665            } => Ok(Self::CreditsUpdated {
666                session_id: SessionId::from_dto(session_id)?,
667                available_credits,
668                max_credits,
669                timestamp,
670            }),
671        }
672    }
673}
674
675impl From<PerformanceMetrics> for PerformanceMetricsDto {
676    fn from(metrics: PerformanceMetrics) -> Self {
677        Self {
678            frames_per_second: metrics.frames_per_second,
679            bytes_per_second: metrics.bytes_per_second,
680            average_frame_size: metrics.average_frame_size,
681            priority_distribution: metrics.priority_distribution,
682            latency_ms: metrics.latency_ms,
683        }
684    }
685}
686
687impl TryFrom<PerformanceMetricsDto> for PerformanceMetrics {
688    type Error = DomainError;
689
690    fn try_from(dto: PerformanceMetricsDto) -> Result<Self, Self::Error> {
691        Ok(Self {
692            frames_per_second: dto.frames_per_second,
693            bytes_per_second: dto.bytes_per_second,
694            average_frame_size: dto.average_frame_size,
695            priority_distribution: dto.priority_distribution,
696            latency_ms: dto.latency_ms,
697        })
698    }
699}
700
701// No conversion needed - PriorityDistributionDto is now an alias
702
703impl From<EventId> for EventIdDto {
704    fn from(event_id: EventId) -> Self {
705        Self::new(event_id.inner())
706    }
707}
708
709impl From<EventIdDto> for EventId {
710    fn from(dto: EventIdDto) -> Self {
711        EventId::from_uuid(dto.uuid)
712    }
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718    use chrono::Utc;
719
720    #[test]
721    fn test_domain_event_dto_conversion() {
722        let session_id = SessionId::new();
723        let stream_id = StreamId::new();
724        let timestamp = Utc::now();
725
726        let domain_event = DomainEvent::StreamCreated {
727            session_id,
728            stream_id,
729            timestamp,
730        };
731
732        // Convert to DTO
733        let dto = domain_event.clone().to_dto();
734
735        // Convert back to domain
736        let converted = DomainEvent::from_dto(dto).unwrap();
737
738        assert_eq!(domain_event, converted);
739    }
740
741    #[test]
742    fn test_performance_metrics_dto_conversion() {
743        let metrics = PerformanceMetrics {
744            frames_per_second: 60.0,
745            bytes_per_second: 1024.0,
746            average_frame_size: 512.0,
747            priority_distribution: PriorityDistributionDto::default(),
748            latency_ms: Some(100),
749        };
750
751        let dto = PerformanceMetricsDto::from(metrics.clone());
752        let converted = PerformanceMetrics::try_from(dto).unwrap();
753
754        assert_eq!(metrics, converted);
755    }
756
757    #[test]
758    fn test_event_dto_serialization() {
759        let session_id = SessionId::new();
760        let event_dto = DomainEventDto::SessionActivated {
761            session_id: session_id.to_dto(),
762            timestamp: Utc::now(),
763        };
764
765        let serialized = serde_json::to_string(&event_dto).unwrap();
766        let deserialized: DomainEventDto = serde_json::from_str(&serialized).unwrap();
767
768        assert_eq!(event_dto, deserialized);
769    }
770
771    #[test]
772    fn test_event_id_dto_creation() {
773        let uuid = uuid::Uuid::new_v4();
774        let event_id_dto = EventIdDto::new(uuid);
775
776        assert_eq!(event_id_dto.uuid(), uuid);
777    }
778
779    #[test]
780    fn test_event_id_dto_generate() {
781        let event_id1 = EventIdDto::generate();
782        let event_id2 = EventIdDto::generate();
783
784        assert_ne!(event_id1, event_id2);
785    }
786
787    #[test]
788    fn test_event_id_dto_display() {
789        let uuid = uuid::Uuid::new_v4();
790        let event_id_dto = EventIdDto::new(uuid);
791
792        assert_eq!(event_id_dto.to_string(), uuid.to_string());
793    }
794
795    #[test]
796    fn test_event_id_dto_conversion() {
797        let event_id = EventId::new();
798        let dto = EventIdDto::from(event_id);
799        let converted_back = EventId::from(dto);
800
801        assert_eq!(event_id, converted_back);
802    }
803
804    #[test]
805    fn test_all_domain_event_dto_variants() {
806        let session_id = SessionId::new();
807        let stream_id = StreamId::new();
808        let timestamp = Utc::now();
809
810        // Test SessionActivated
811        let event1 = DomainEvent::SessionActivated {
812            session_id,
813            timestamp,
814        };
815        let dto1 = DomainEventDto::from(event1.clone());
816        let converted1 = DomainEvent::from_dto(dto1).unwrap();
817        assert_eq!(event1, converted1);
818
819        // Test SessionClosed
820        let event2 = DomainEvent::SessionClosed {
821            session_id,
822            timestamp,
823        };
824        let dto2 = DomainEventDto::from(event2.clone());
825        let converted2 = DomainEvent::from_dto(dto2).unwrap();
826        assert_eq!(event2, converted2);
827
828        // Test SessionExpired
829        let event3 = DomainEvent::SessionExpired {
830            session_id,
831            timestamp,
832        };
833        let dto3 = DomainEventDto::from(event3.clone());
834        let converted3 = DomainEvent::from_dto(dto3).unwrap();
835        assert_eq!(event3, converted3);
836
837        // Test StreamCreated
838        let event4 = DomainEvent::StreamCreated {
839            session_id,
840            stream_id,
841            timestamp,
842        };
843        let dto4 = DomainEventDto::from(event4.clone());
844        let converted4 = DomainEvent::from_dto(dto4).unwrap();
845        assert_eq!(event4, converted4);
846
847        // Test StreamStarted
848        let event5 = DomainEvent::StreamStarted {
849            session_id,
850            stream_id,
851            timestamp,
852        };
853        let dto5 = DomainEventDto::from(event5.clone());
854        let converted5 = DomainEvent::from_dto(dto5).unwrap();
855        assert_eq!(event5, converted5);
856
857        // Test StreamCompleted
858        let event6 = DomainEvent::StreamCompleted {
859            session_id,
860            stream_id,
861            timestamp,
862        };
863        let dto6 = DomainEventDto::from(event6.clone());
864        let converted6 = DomainEvent::from_dto(dto6).unwrap();
865        assert_eq!(event6, converted6);
866    }
867
868    #[test]
869    fn test_stream_failed_event_conversion() {
870        let session_id = SessionId::new();
871        let stream_id = StreamId::new();
872        let timestamp = Utc::now();
873        let error = "Test error message".to_string();
874
875        let event = DomainEvent::StreamFailed {
876            session_id,
877            stream_id,
878            error: error.clone(),
879            timestamp,
880        };
881
882        let dto = DomainEventDto::from(event.clone());
883        let converted = DomainEvent::from_dto(dto).unwrap();
884
885        assert_eq!(event, converted);
886    }
887
888    #[test]
889    fn test_performance_metrics_with_none_latency() {
890        let metrics = PerformanceMetrics {
891            frames_per_second: 30.0,
892            bytes_per_second: 2048.0,
893            average_frame_size: 1024.0,
894            priority_distribution: PriorityDistributionDto::default(),
895            latency_ms: None,
896        };
897
898        let dto = PerformanceMetricsDto::from(metrics.clone());
899        let converted = PerformanceMetrics::try_from(dto).unwrap();
900
901        assert_eq!(metrics, converted);
902        assert_eq!(converted.latency_ms, None);
903    }
904
905    #[test]
906    fn test_complex_event_serialization() {
907        let session_id = SessionId::new();
908        let stream_id = StreamId::new();
909        let timestamp = Utc::now();
910
911        let event_dto = DomainEventDto::PatchFramesGenerated {
912            session_id: session_id.to_dto(),
913            stream_id: stream_id.to_dto(),
914            frame_count: 42,
915            total_bytes: 1024,
916            highest_priority: 100,
917            timestamp,
918        };
919
920        let serialized = serde_json::to_string(&event_dto).unwrap();
921        let deserialized: DomainEventDto = serde_json::from_str(&serialized).unwrap();
922
923        assert_eq!(event_dto, deserialized);
924    }
925
926    #[test]
927    fn test_performance_metrics_dto_fields() {
928        let dto = PerformanceMetricsDto {
929            frames_per_second: 60.0,
930            bytes_per_second: 4096.0,
931            average_frame_size: 256.0,
932            priority_distribution: PriorityDistributionDto::default(),
933            latency_ms: Some(50),
934        };
935
936        assert_eq!(dto.frames_per_second, 60.0);
937        assert_eq!(dto.bytes_per_second, 4096.0);
938        assert_eq!(dto.average_frame_size, 256.0);
939        assert_eq!(dto.latency_ms, Some(50));
940    }
941
942    #[test]
943    fn test_priority_threshold_adjusted_event() {
944        let session_id = SessionId::new();
945        let timestamp = Utc::now();
946
947        let event = DomainEvent::PriorityThresholdAdjusted {
948            session_id,
949            old_threshold: 50,
950            new_threshold: 75,
951            reason: "Performance optimization".to_string(),
952            timestamp,
953        };
954
955        let dto = DomainEventDto::from(event.clone());
956        let converted = DomainEvent::from_dto(dto).unwrap();
957
958        assert_eq!(event, converted);
959    }
960
961    #[test]
962    fn test_event_id_dto_hash() {
963        let uuid1 = uuid::Uuid::new_v4();
964        let uuid2 = uuid::Uuid::new_v4();
965
966        let event_id1 = EventIdDto::new(uuid1);
967        let event_id2 = EventIdDto::new(uuid2);
968        let event_id3 = EventIdDto::new(uuid1); // Same UUID as event_id1
969
970        use std::collections::HashSet;
971        let mut set = HashSet::new();
972        set.insert(event_id1.clone());
973        set.insert(event_id2);
974        set.insert(event_id3);
975
976        assert_eq!(set.len(), 2); // Only 2 unique UUIDs
977    }
978
979    #[test]
980    fn test_event_dto_clone() {
981        let session_id = SessionId::new();
982        let timestamp = Utc::now();
983
984        let original = DomainEventDto::SessionActivated {
985            session_id: session_id.to_dto(),
986            timestamp,
987        };
988
989        let cloned = original.clone();
990        assert_eq!(original, cloned);
991    }
992
993    #[test]
994    fn test_event_dto_debug() {
995        let session_id = SessionId::new();
996        let timestamp = Utc::now();
997
998        let event = DomainEventDto::SessionActivated {
999            session_id: session_id.to_dto(),
1000            timestamp,
1001        };
1002
1003        let debug_str = format!("{:?}", event);
1004        assert!(debug_str.contains("SessionActivated"));
1005    }
1006}