Skip to main content

things3_cli/events/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use things3_core::ThingsId;
4use uuid::Uuid;
5
6/// Event types for Things 3 entities
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8#[serde(tag = "event_type")]
9pub enum EventType {
10    /// Task events
11    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    /// Project events
28    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    /// Area events
42    AreaCreated {
43        area_id: ThingsId,
44    },
45    AreaUpdated {
46        area_id: ThingsId,
47    },
48    AreaDeleted {
49        area_id: ThingsId,
50    },
51
52    /// Progress events
53    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/// Event data structure
68#[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        // Test all task event types
141        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        // Test all project event types
156        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        // Test all area event types
168        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        // Test all progress event types
177        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        // All should compile without errors
183    }
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        // Test all event types
193        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        // Mirror the production event_entity_id match in EventFilter::matches.
231        // Entity events yield Some(ThingsId); progress events yield None because
232        // operation_id is an internal Uuid, not a ThingsId entity identifier.
233        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        // Serialize to JSON
306        let json = serde_json::to_string(&original_event).unwrap();
307
308        // Deserialize back to Event
309        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}