1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use things3_core::ThingsId;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8#[serde(tag = "event_type")]
9pub enum EventType {
10 TaskCreated {
12 task_id: ThingsId,
13 },
14 TaskUpdated {
15 task_id: ThingsId,
16 },
17 TaskDeleted {
18 task_id: ThingsId,
19 },
20 TaskCompleted {
21 task_id: ThingsId,
22 },
23 TaskCancelled {
24 task_id: ThingsId,
25 },
26
27 ProjectCreated {
29 project_id: ThingsId,
30 },
31 ProjectUpdated {
32 project_id: ThingsId,
33 },
34 ProjectDeleted {
35 project_id: ThingsId,
36 },
37 ProjectCompleted {
38 project_id: ThingsId,
39 },
40
41 AreaCreated {
43 area_id: ThingsId,
44 },
45 AreaUpdated {
46 area_id: ThingsId,
47 },
48 AreaDeleted {
49 area_id: ThingsId,
50 },
51
52 ProgressStarted {
54 operation_id: Uuid,
55 },
56 ProgressUpdated {
57 operation_id: Uuid,
58 },
59 ProgressCompleted {
60 operation_id: Uuid,
61 },
62 ProgressFailed {
63 operation_id: Uuid,
64 },
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct Event {
70 pub id: Uuid,
71 pub event_type: EventType,
72 pub timestamp: DateTime<Utc>,
73 pub data: Option<serde_json::Value>,
74 pub source: String,
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use chrono::Utc;
81 use things3_core::ThingsId;
82 use uuid::Uuid;
83
84 #[test]
85 fn test_event_creation() {
86 let event = Event {
87 id: Uuid::new_v4(),
88 event_type: EventType::TaskCreated {
89 task_id: ThingsId::new_v4(),
90 },
91 timestamp: Utc::now(),
92 data: None,
93 source: "test".to_string(),
94 };
95
96 assert!(!event.id.is_nil());
97 assert_eq!(event.source, "test");
98 }
99
100 #[test]
101 fn test_event_creation_with_data() {
102 let event = Event {
103 id: Uuid::new_v4(),
104 event_type: EventType::TaskCreated {
105 task_id: ThingsId::new_v4(),
106 },
107 timestamp: Utc::now(),
108 data: Some(serde_json::json!({"key": "value"})),
109 source: "test".to_string(),
110 };
111
112 assert!(!event.id.is_nil());
113 assert_eq!(event.source, "test");
114 assert!(event.data.is_some());
115 }
116
117 #[tokio::test]
118 async fn test_event_creation_without_data() {
119 let event = Event {
120 event_type: EventType::TaskCreated {
121 task_id: ThingsId::new_v4(),
122 },
123 id: Uuid::new_v4(),
124 source: "test".to_string(),
125 timestamp: Utc::now(),
126 data: None,
127 };
128
129 assert_eq!(event.source, "test");
130 assert!(event.data.is_none());
131 }
132
133 #[test]
134 fn test_all_event_types_creation() {
135 let task_id = ThingsId::new_v4();
136 let project_id = ThingsId::new_v4();
137 let area_id = ThingsId::new_v4();
138 let operation_id = Uuid::new_v4();
139
140 let _ = EventType::TaskCreated {
142 task_id: task_id.clone(),
143 };
144 let _ = EventType::TaskUpdated {
145 task_id: task_id.clone(),
146 };
147 let _ = EventType::TaskDeleted {
148 task_id: task_id.clone(),
149 };
150 let _ = EventType::TaskCompleted {
151 task_id: task_id.clone(),
152 };
153 let _ = EventType::TaskCancelled { task_id };
154
155 let _ = EventType::ProjectCreated {
157 project_id: project_id.clone(),
158 };
159 let _ = EventType::ProjectUpdated {
160 project_id: project_id.clone(),
161 };
162 let _ = EventType::ProjectDeleted {
163 project_id: project_id.clone(),
164 };
165 let _ = EventType::ProjectCompleted { project_id };
166
167 let _ = EventType::AreaCreated {
169 area_id: area_id.clone(),
170 };
171 let _ = EventType::AreaUpdated {
172 area_id: area_id.clone(),
173 };
174 let _ = EventType::AreaDeleted { area_id };
175
176 let _ = EventType::ProgressStarted { operation_id };
178 let _ = EventType::ProgressUpdated { operation_id };
179 let _ = EventType::ProgressCompleted { operation_id };
180 let _ = EventType::ProgressFailed { operation_id };
181
182 }
184
185 #[tokio::test]
186 async fn test_event_type_entity_id_extraction_comprehensive() {
187 let task_id = ThingsId::new_v4();
188 let project_id = ThingsId::new_v4();
189 let area_id = ThingsId::new_v4();
190 let operation_id = Uuid::new_v4();
191
192 let events = vec![
194 EventType::TaskCreated {
195 task_id: task_id.clone(),
196 },
197 EventType::TaskUpdated {
198 task_id: task_id.clone(),
199 },
200 EventType::TaskDeleted {
201 task_id: task_id.clone(),
202 },
203 EventType::TaskCompleted {
204 task_id: task_id.clone(),
205 },
206 EventType::TaskCancelled { task_id },
207 EventType::ProjectCreated {
208 project_id: project_id.clone(),
209 },
210 EventType::ProjectUpdated {
211 project_id: project_id.clone(),
212 },
213 EventType::ProjectDeleted {
214 project_id: project_id.clone(),
215 },
216 EventType::ProjectCompleted { project_id },
217 EventType::AreaCreated {
218 area_id: area_id.clone(),
219 },
220 EventType::AreaUpdated {
221 area_id: area_id.clone(),
222 },
223 EventType::AreaDeleted { area_id },
224 EventType::ProgressStarted { operation_id },
225 EventType::ProgressUpdated { operation_id },
226 EventType::ProgressCompleted { operation_id },
227 EventType::ProgressFailed { operation_id },
228 ];
229
230 for event_type in &events {
234 let extracted_id: Option<&ThingsId> = match event_type {
235 EventType::TaskCreated { task_id }
236 | EventType::TaskUpdated { task_id }
237 | EventType::TaskDeleted { task_id }
238 | EventType::TaskCompleted { task_id }
239 | EventType::TaskCancelled { task_id } => Some(task_id),
240 EventType::ProjectCreated { project_id }
241 | EventType::ProjectUpdated { project_id }
242 | EventType::ProjectDeleted { project_id }
243 | EventType::ProjectCompleted { project_id } => Some(project_id),
244 EventType::AreaCreated { area_id }
245 | EventType::AreaUpdated { area_id }
246 | EventType::AreaDeleted { area_id } => Some(area_id),
247 EventType::ProgressStarted { .. }
248 | EventType::ProgressUpdated { .. }
249 | EventType::ProgressCompleted { .. }
250 | EventType::ProgressFailed { .. } => None,
251 };
252
253 let is_progress = matches!(
254 event_type,
255 EventType::ProgressStarted { .. }
256 | EventType::ProgressUpdated { .. }
257 | EventType::ProgressCompleted { .. }
258 | EventType::ProgressFailed { .. }
259 );
260 if is_progress {
261 assert!(
262 extracted_id.is_none(),
263 "progress events must not have a ThingsId"
264 );
265 } else {
266 assert!(
267 extracted_id.is_some(),
268 "entity events must carry a ThingsId"
269 );
270 }
271 }
272 }
273
274 #[test]
275 fn test_event_serialization() {
276 let event = Event {
277 id: Uuid::new_v4(),
278 event_type: EventType::TaskCreated {
279 task_id: ThingsId::new_v4(),
280 },
281 timestamp: Utc::now(),
282 data: Some(serde_json::json!({"key": "value"})),
283 source: "test".to_string(),
284 };
285
286 let json = serde_json::to_string(&event).unwrap();
287 let deserialized: Event = serde_json::from_str(&json).unwrap();
288
289 assert_eq!(event.id, deserialized.id);
290 assert_eq!(event.source, deserialized.source);
291 }
292
293 #[tokio::test]
294 async fn test_event_serialization_roundtrip() {
295 let original_event = Event {
296 event_type: EventType::TaskCreated {
297 task_id: ThingsId::new_v4(),
298 },
299 id: Uuid::new_v4(),
300 source: "test".to_string(),
301 timestamp: Utc::now(),
302 data: Some(serde_json::json!({"title": "Test Task"})),
303 };
304
305 let json = serde_json::to_string(&original_event).unwrap();
307
308 let deserialized_event: Event = serde_json::from_str(&json).unwrap();
310
311 assert_eq!(original_event.event_type, deserialized_event.event_type);
312 assert_eq!(original_event.id, deserialized_event.id);
313 assert_eq!(original_event.source, deserialized_event.source);
314 assert_eq!(original_event.data, deserialized_event.data);
315 }
316}