intent_engine/
events.rs

1use crate::db::models::Event;
2use crate::error::{IntentError, Result};
3use chrono::Utc;
4use sqlx::SqlitePool;
5
6pub struct EventManager<'a> {
7    pool: &'a SqlitePool,
8}
9
10impl<'a> EventManager<'a> {
11    pub fn new(pool: &'a SqlitePool) -> Self {
12        Self { pool }
13    }
14
15    /// Add a new event
16    pub async fn add_event(
17        &self,
18        task_id: i64,
19        log_type: &str,
20        discussion_data: &str,
21    ) -> Result<Event> {
22        // Check if task exists
23        let task_exists: bool =
24            sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tasks WHERE id = ?)")
25                .bind(task_id)
26                .fetch_one(self.pool)
27                .await?;
28
29        if !task_exists {
30            return Err(IntentError::TaskNotFound(task_id));
31        }
32
33        let now = Utc::now();
34
35        let result = sqlx::query(
36            r#"
37            INSERT INTO events (task_id, log_type, discussion_data, timestamp)
38            VALUES (?, ?, ?, ?)
39            "#,
40        )
41        .bind(task_id)
42        .bind(log_type)
43        .bind(discussion_data)
44        .bind(now)
45        .execute(self.pool)
46        .await?;
47
48        let id = result.last_insert_rowid();
49
50        Ok(Event {
51            id,
52            task_id,
53            timestamp: now,
54            log_type: log_type.to_string(),
55            discussion_data: discussion_data.to_string(),
56        })
57    }
58
59    /// List events for a task
60    pub async fn list_events(&self, task_id: i64, limit: Option<i64>) -> Result<Vec<Event>> {
61        // Check if task exists
62        let task_exists: bool =
63            sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tasks WHERE id = ?)")
64                .bind(task_id)
65                .fetch_one(self.pool)
66                .await?;
67
68        if !task_exists {
69            return Err(IntentError::TaskNotFound(task_id));
70        }
71
72        let limit = limit.unwrap_or(100);
73
74        let events = sqlx::query_as::<_, Event>(
75            r#"
76            SELECT id, task_id, timestamp, log_type, discussion_data
77            FROM events
78            WHERE task_id = ?
79            ORDER BY timestamp DESC
80            LIMIT ?
81            "#,
82        )
83        .bind(task_id)
84        .bind(limit)
85        .fetch_all(self.pool)
86        .await?;
87
88        Ok(events)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::tasks::TaskManager;
96    use crate::test_utils::test_helpers::TestContext;
97
98    #[tokio::test]
99    async fn test_add_event() {
100        let ctx = TestContext::new().await;
101        let task_mgr = TaskManager::new(ctx.pool());
102        let event_mgr = EventManager::new(ctx.pool());
103
104        let task = task_mgr.add_task("Test task", None, None).await.unwrap();
105        let event = event_mgr
106            .add_event(task.id, "decision", "Test decision")
107            .await
108            .unwrap();
109
110        assert_eq!(event.task_id, task.id);
111        assert_eq!(event.log_type, "decision");
112        assert_eq!(event.discussion_data, "Test decision");
113    }
114
115    #[tokio::test]
116    async fn test_add_event_nonexistent_task() {
117        let ctx = TestContext::new().await;
118        let event_mgr = EventManager::new(ctx.pool());
119
120        let result = event_mgr.add_event(999, "decision", "Test").await;
121        assert!(matches!(result, Err(IntentError::TaskNotFound(999))));
122    }
123
124    #[tokio::test]
125    async fn test_list_events() {
126        let ctx = TestContext::new().await;
127        let task_mgr = TaskManager::new(ctx.pool());
128        let event_mgr = EventManager::new(ctx.pool());
129
130        let task = task_mgr.add_task("Test task", None, None).await.unwrap();
131
132        // Add multiple events
133        event_mgr
134            .add_event(task.id, "decision", "Decision 1")
135            .await
136            .unwrap();
137        event_mgr
138            .add_event(task.id, "blocker", "Blocker 1")
139            .await
140            .unwrap();
141        event_mgr
142            .add_event(task.id, "milestone", "Milestone 1")
143            .await
144            .unwrap();
145
146        let events = event_mgr.list_events(task.id, None).await.unwrap();
147        assert_eq!(events.len(), 3);
148
149        // Events should be in reverse chronological order
150        assert_eq!(events[0].log_type, "milestone");
151        assert_eq!(events[1].log_type, "blocker");
152        assert_eq!(events[2].log_type, "decision");
153    }
154
155    #[tokio::test]
156    async fn test_list_events_with_limit() {
157        let ctx = TestContext::new().await;
158        let task_mgr = TaskManager::new(ctx.pool());
159        let event_mgr = EventManager::new(ctx.pool());
160
161        let task = task_mgr.add_task("Test task", None, None).await.unwrap();
162
163        // Add 5 events
164        for i in 0..5 {
165            event_mgr
166                .add_event(task.id, "test", &format!("Event {}", i))
167                .await
168                .unwrap();
169        }
170
171        let events = event_mgr.list_events(task.id, Some(3)).await.unwrap();
172        assert_eq!(events.len(), 3);
173    }
174
175    #[tokio::test]
176    async fn test_list_events_nonexistent_task() {
177        let ctx = TestContext::new().await;
178        let event_mgr = EventManager::new(ctx.pool());
179
180        let result = event_mgr.list_events(999, None).await;
181        assert!(matches!(result, Err(IntentError::TaskNotFound(999))));
182    }
183
184    #[tokio::test]
185    async fn test_list_events_empty() {
186        let ctx = TestContext::new().await;
187        let task_mgr = TaskManager::new(ctx.pool());
188        let event_mgr = EventManager::new(ctx.pool());
189
190        let task = task_mgr.add_task("Test task", None, None).await.unwrap();
191
192        let events = event_mgr.list_events(task.id, None).await.unwrap();
193        assert_eq!(events.len(), 0);
194    }
195}