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