1use super::store::TaskStore;
7use super::types::{NewTask, Task, TaskError, TaskId, TaskStatus, TaskUpdate};
8use async_trait::async_trait;
9
10#[cfg(target_arch = "wasm32")]
11use crate::tokio::sync::RwLock;
12#[cfg(not(target_arch = "wasm32"))]
13use tokio::sync::RwLock;
14
15pub struct MemoryTaskStore {
41 tasks: RwLock<Vec<Task>>,
42}
43
44impl MemoryTaskStore {
45 pub fn new() -> Self {
47 Self {
48 tasks: RwLock::new(Vec::new()),
49 }
50 }
51
52 pub fn with_tasks(tasks: Vec<Task>) -> Self {
56 Self {
57 tasks: RwLock::new(tasks),
58 }
59 }
60
61 pub fn len(&self) -> usize {
65 self.tasks.try_read().ok().map_or(0, |t| t.len())
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.len() == 0
71 }
72}
73
74impl Default for MemoryTaskStore {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
81#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
82impl TaskStore for MemoryTaskStore {
83 async fn list(&self) -> Result<Vec<Task>, TaskError> {
84 let tasks = self.tasks.read().await;
85 Ok(tasks.clone())
86 }
87
88 async fn get(&self, id: &TaskId) -> Result<Option<Task>, TaskError> {
89 let tasks = self.tasks.read().await;
90 Ok(tasks.iter().find(|t| &t.id == id).cloned())
91 }
92
93 async fn create(&self, new_task: NewTask, session_id: Option<&str>) -> Result<Task, TaskError> {
94 let now = chrono::Utc::now().to_rfc3339();
95 let task = Task {
96 id: TaskId::new(),
97 subject: new_task.subject,
98 description: new_task.description,
99 status: TaskStatus::default(),
100 priority: new_task.priority.unwrap_or_default(),
101 labels: new_task.labels.unwrap_or_default(),
102 blocks: new_task.blocks.unwrap_or_default(),
103 created_at: now.clone(),
104 updated_at: now,
105 created_by_session: session_id.map(String::from),
106 updated_by_session: session_id.map(String::from),
107 owner: new_task.owner,
108 metadata: new_task.metadata.unwrap_or_default(),
109 blocked_by: new_task.blocked_by.unwrap_or_default(),
110 };
111
112 let mut tasks = self.tasks.write().await;
113 tasks.push(task.clone());
114 Ok(task)
115 }
116
117 async fn update(
118 &self,
119 id: &TaskId,
120 update: TaskUpdate,
121 session_id: Option<&str>,
122 ) -> Result<Task, TaskError> {
123 let mut tasks = self.tasks.write().await;
124 let task = tasks
125 .iter_mut()
126 .find(|t| &t.id == id)
127 .ok_or_else(|| TaskError::NotFound(id.0.clone()))?;
128
129 if let Some(subject) = update.subject {
130 task.subject = subject;
131 }
132 if let Some(description) = update.description {
133 task.description = description;
134 }
135 if let Some(status) = update.status {
136 task.status = status;
137 }
138 if let Some(priority) = update.priority {
139 task.priority = priority;
140 }
141 if let Some(labels) = update.labels {
142 task.labels = labels;
143 }
144 if let Some(add_blocks) = update.add_blocks {
145 for block_id in add_blocks {
146 if !task.blocks.contains(&block_id) {
147 task.blocks.push(block_id);
148 }
149 }
150 }
151 if let Some(remove_blocks) = update.remove_blocks {
152 task.blocks.retain(|b| !remove_blocks.contains(b));
153 }
154
155 if let Some(owner) = update.owner {
157 task.owner = Some(owner);
158 }
159 if let Some(metadata) = update.metadata {
160 for (key, value) in metadata {
161 if value.is_null() {
162 task.metadata.remove(&key);
164 } else {
165 task.metadata.insert(key, value);
166 }
167 }
168 }
169 if let Some(add_blocked_by) = update.add_blocked_by {
170 for block_id in add_blocked_by {
171 if !task.blocked_by.contains(&block_id) {
172 task.blocked_by.push(block_id);
173 }
174 }
175 }
176 if let Some(remove_blocked_by) = update.remove_blocked_by {
177 task.blocked_by.retain(|b| !remove_blocked_by.contains(b));
178 }
179
180 task.updated_at = chrono::Utc::now().to_rfc3339();
181 task.updated_by_session = session_id.map(String::from);
182
183 Ok(task.clone())
184 }
185
186 async fn delete(&self, id: &TaskId) -> Result<(), TaskError> {
187 let mut tasks = self.tasks.write().await;
188 let len_before = tasks.len();
189 tasks.retain(|t| &t.id != id);
190 if tasks.len() == len_before {
191 return Err(TaskError::NotFound(id.0.clone()));
192 }
193 Ok(())
194 }
195}
196
197#[cfg(test)]
198#[allow(clippy::unwrap_used, clippy::expect_used)]
199mod tests {
200 use super::*;
201 use crate::builtin::types::{TaskPriority, TaskStatus};
202
203 #[tokio::test]
204 async fn test_memory_store_create_and_get() {
205 let store = MemoryTaskStore::new();
206
207 let new_task = NewTask {
208 subject: "Test task".to_string(),
209 description: "Test description".to_string(),
210 priority: Some(TaskPriority::High),
211 labels: Some(vec!["test".to_string(), "important".to_string()]),
212 blocks: None,
213 ..Default::default()
214 };
215
216 let created = store.create(new_task, Some("session-1")).await.unwrap();
217
218 assert_eq!(created.subject, "Test task");
220 assert_eq!(created.description, "Test description");
221 assert_eq!(created.priority, TaskPriority::High);
222 assert_eq!(
223 created.labels,
224 vec!["test".to_string(), "important".to_string()]
225 );
226 assert_eq!(created.status, TaskStatus::Pending);
227 assert_eq!(created.created_by_session, Some("session-1".to_string()));
228 assert_eq!(created.updated_by_session, Some("session-1".to_string()));
229 assert!(!created.created_at.is_empty());
230 assert!(!created.updated_at.is_empty());
231 assert_eq!(created.id.0.len(), 36); let fetched = store.get(&created.id).await.unwrap();
235 assert!(fetched.is_some());
236 let fetched = fetched.unwrap();
237 assert_eq!(fetched.id, created.id);
238 assert_eq!(fetched.subject, created.subject);
239 assert_eq!(fetched.description, created.description);
240 }
241
242 #[tokio::test]
243 async fn test_memory_store_create_with_defaults() {
244 let store = MemoryTaskStore::new();
245
246 let new_task = NewTask {
247 subject: "Simple task".to_string(),
248 description: "No optional fields".to_string(),
249 priority: None,
250 labels: None,
251 blocks: None,
252 ..Default::default()
253 };
254
255 let created = store.create(new_task, None).await.unwrap();
256
257 assert_eq!(created.priority, TaskPriority::Medium);
259 assert!(created.labels.is_empty());
260 assert!(created.blocks.is_empty());
261 assert!(created.created_by_session.is_none());
262 assert!(created.updated_by_session.is_none());
263 }
264
265 #[tokio::test]
266 async fn test_memory_store_get_nonexistent() {
267 let store = MemoryTaskStore::new();
268
269 let result = store
270 .get(&TaskId::from_string("nonexistent"))
271 .await
272 .unwrap();
273 assert!(result.is_none());
274 }
275
276 #[tokio::test]
277 async fn test_memory_store_list() {
278 let store = MemoryTaskStore::new();
279
280 let tasks = store.list().await.unwrap();
282 assert!(tasks.is_empty());
283
284 let task1 = NewTask {
286 subject: "Task 1".to_string(),
287 description: "First task".to_string(),
288 priority: Some(TaskPriority::Low),
289 labels: None,
290 blocks: None,
291 ..Default::default()
292 };
293 let task2 = NewTask {
294 subject: "Task 2".to_string(),
295 description: "Second task".to_string(),
296 priority: Some(TaskPriority::High),
297 labels: None,
298 blocks: None,
299 ..Default::default()
300 };
301 let task3 = NewTask {
302 subject: "Task 3".to_string(),
303 description: "Third task".to_string(),
304 priority: None,
305 labels: Some(vec!["urgent".to_string()]),
306 blocks: None,
307 ..Default::default()
308 };
309
310 let created1 = store.create(task1, None).await.unwrap();
311 let created2 = store.create(task2, None).await.unwrap();
312 let created3 = store.create(task3, None).await.unwrap();
313
314 let tasks = store.list().await.unwrap();
316 assert_eq!(tasks.len(), 3);
317
318 let ids: Vec<_> = tasks.iter().map(|t| &t.id).collect();
320 assert!(ids.contains(&&created1.id));
321 assert!(ids.contains(&&created2.id));
322 assert!(ids.contains(&&created3.id));
323 }
324
325 #[tokio::test]
326 async fn test_memory_store_update() {
327 let store = MemoryTaskStore::new();
328
329 let new_task = NewTask {
330 subject: "Original subject".to_string(),
331 description: "Original description".to_string(),
332 priority: Some(TaskPriority::Low),
333 labels: Some(vec!["initial".to_string()]),
334 blocks: None,
335 ..Default::default()
336 };
337
338 let created = store.create(new_task, Some("session-1")).await.unwrap();
339 let original_created_at = created.created_at.clone();
340
341 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
343
344 let update = TaskUpdate {
346 subject: Some("Updated subject".to_string()),
347 description: Some("Updated description".to_string()),
348 status: Some(TaskStatus::InProgress),
349 priority: Some(TaskPriority::High),
350 labels: Some(vec!["updated".to_string(), "reviewed".to_string()]),
351 add_blocks: None,
352 remove_blocks: None,
353 owner: None,
354 metadata: None,
355 add_blocked_by: None,
356 remove_blocked_by: None,
357 };
358
359 let updated = store
360 .update(&created.id, update, Some("session-2"))
361 .await
362 .unwrap();
363
364 assert_eq!(updated.id, created.id);
366 assert_eq!(updated.subject, "Updated subject");
367 assert_eq!(updated.description, "Updated description");
368 assert_eq!(updated.status, TaskStatus::InProgress);
369 assert_eq!(updated.priority, TaskPriority::High);
370 assert_eq!(
371 updated.labels,
372 vec!["updated".to_string(), "reviewed".to_string()]
373 );
374 assert_eq!(updated.created_at, original_created_at); assert_eq!(updated.created_by_session, Some("session-1".to_string())); assert_eq!(updated.updated_by_session, Some("session-2".to_string()));
377 assert_ne!(updated.updated_at, original_created_at); }
379
380 #[tokio::test]
381 async fn test_memory_store_partial_update() {
382 let store = MemoryTaskStore::new();
383
384 let new_task = NewTask {
385 subject: "Original".to_string(),
386 description: "Original desc".to_string(),
387 priority: Some(TaskPriority::High),
388 labels: Some(vec!["keep".to_string()]),
389 blocks: None,
390 ..Default::default()
391 };
392
393 let created = store.create(new_task, None).await.unwrap();
394
395 let update = TaskUpdate {
397 subject: Some("New subject".to_string()),
398 ..Default::default()
399 };
400
401 let updated = store.update(&created.id, update, None).await.unwrap();
402
403 assert_eq!(updated.subject, "New subject");
405 assert_eq!(updated.description, "Original desc");
406 assert_eq!(updated.priority, TaskPriority::High);
407 assert_eq!(updated.labels, vec!["keep".to_string()]);
408 assert_eq!(updated.status, TaskStatus::Pending);
409 }
410
411 #[tokio::test]
412 async fn test_memory_store_update_not_found() {
413 let store = MemoryTaskStore::new();
414
415 let update = TaskUpdate {
416 subject: Some("Updated".to_string()),
417 ..Default::default()
418 };
419
420 let result = store
421 .update(&TaskId::from_string("nonexistent"), update, None)
422 .await;
423
424 assert!(matches!(result, Err(TaskError::NotFound(id)) if id == "nonexistent"));
425 }
426
427 #[tokio::test]
428 async fn test_memory_store_delete() {
429 let store = MemoryTaskStore::new();
430
431 let new_task = NewTask {
432 subject: "To delete".to_string(),
433 description: "Will be deleted".to_string(),
434 priority: None,
435 labels: None,
436 blocks: None,
437 ..Default::default()
438 };
439
440 let created = store.create(new_task, None).await.unwrap();
441
442 assert!(store.get(&created.id).await.unwrap().is_some());
444 assert_eq!(store.len(), 1);
445
446 store.delete(&created.id).await.unwrap();
448
449 assert!(store.get(&created.id).await.unwrap().is_none());
451 assert_eq!(store.len(), 0);
452 assert!(store.is_empty());
453 }
454
455 #[tokio::test]
456 async fn test_memory_store_delete_not_found() {
457 let store = MemoryTaskStore::new();
458
459 let result = store.delete(&TaskId::from_string("nonexistent")).await;
460
461 assert!(matches!(result, Err(TaskError::NotFound(id)) if id == "nonexistent"));
462 }
463
464 #[tokio::test]
465 async fn test_memory_store_delete_multiple() {
466 let store = MemoryTaskStore::new();
467
468 for i in 1..=5 {
470 store
471 .create(
472 NewTask {
473 subject: format!("Task {i}"),
474 description: "".to_string(),
475 priority: None,
476 labels: None,
477 blocks: None,
478 ..Default::default()
479 },
480 None,
481 )
482 .await
483 .unwrap();
484 }
485
486 assert_eq!(store.len(), 5);
487
488 let tasks = store.list().await.unwrap();
489
490 store.delete(&tasks[1].id).await.unwrap();
492 store.delete(&tasks[3].id).await.unwrap();
493
494 assert_eq!(store.len(), 3);
495
496 assert!(store.get(&tasks[0].id).await.unwrap().is_some());
498 assert!(store.get(&tasks[1].id).await.unwrap().is_none());
499 assert!(store.get(&tasks[2].id).await.unwrap().is_some());
500 assert!(store.get(&tasks[3].id).await.unwrap().is_none());
501 assert!(store.get(&tasks[4].id).await.unwrap().is_some());
502 }
503
504 #[tokio::test]
505 async fn test_memory_store_add_blocks() {
506 let store = MemoryTaskStore::new();
507
508 let task1 = store
510 .create(
511 NewTask {
512 subject: "Task 1".to_string(),
513 description: "".to_string(),
514 priority: None,
515 labels: None,
516 blocks: None,
517 ..Default::default()
518 },
519 None,
520 )
521 .await
522 .unwrap();
523
524 let task2 = store
525 .create(
526 NewTask {
527 subject: "Task 2".to_string(),
528 description: "".to_string(),
529 priority: None,
530 labels: None,
531 blocks: None,
532 ..Default::default()
533 },
534 None,
535 )
536 .await
537 .unwrap();
538
539 let task3 = store
540 .create(
541 NewTask {
542 subject: "Task 3".to_string(),
543 description: "".to_string(),
544 priority: None,
545 labels: None,
546 blocks: None,
547 ..Default::default()
548 },
549 None,
550 )
551 .await
552 .unwrap();
553
554 let update = TaskUpdate {
556 add_blocks: Some(vec![task2.id.clone(), task3.id.clone()]),
557 ..Default::default()
558 };
559
560 let updated = store.update(&task1.id, update, None).await.unwrap();
561
562 assert_eq!(updated.blocks.len(), 2);
563 assert!(updated.blocks.contains(&task2.id));
564 assert!(updated.blocks.contains(&task3.id));
565
566 let update = TaskUpdate {
568 add_blocks: Some(vec![task2.id.clone()]),
569 ..Default::default()
570 };
571
572 let updated = store.update(&task1.id, update, None).await.unwrap();
573 assert_eq!(updated.blocks.len(), 2); }
575
576 #[tokio::test]
577 async fn test_memory_store_remove_blocks() {
578 let store = MemoryTaskStore::new();
579
580 let blocker1 = TaskId::from_string("blocker-1");
582 let blocker2 = TaskId::from_string("blocker-2");
583 let blocker3 = TaskId::from_string("blocker-3");
584
585 let task = store
586 .create(
587 NewTask {
588 subject: "Main task".to_string(),
589 description: "".to_string(),
590 priority: None,
591 labels: None,
592 blocks: Some(vec![blocker1.clone(), blocker2.clone(), blocker3.clone()]),
593 ..Default::default()
594 },
595 None,
596 )
597 .await
598 .unwrap();
599
600 assert_eq!(task.blocks.len(), 3);
601
602 let update = TaskUpdate {
604 remove_blocks: Some(vec![blocker2.clone()]),
605 ..Default::default()
606 };
607
608 let updated = store.update(&task.id, update, None).await.unwrap();
609 assert_eq!(updated.blocks.len(), 2);
610 assert!(updated.blocks.contains(&blocker1));
611 assert!(!updated.blocks.contains(&blocker2));
612 assert!(updated.blocks.contains(&blocker3));
613
614 let update = TaskUpdate {
616 remove_blocks: Some(vec![blocker1.clone(), blocker3.clone()]),
617 ..Default::default()
618 };
619
620 let updated = store.update(&task.id, update, None).await.unwrap();
621 assert!(updated.blocks.is_empty());
622 }
623
624 #[tokio::test]
625 async fn test_memory_store_add_and_remove_blocks_same_update() {
626 let store = MemoryTaskStore::new();
627
628 let blocker1 = TaskId::from_string("blocker-1");
629 let blocker2 = TaskId::from_string("blocker-2");
630
631 let task = store
632 .create(
633 NewTask {
634 subject: "Task".to_string(),
635 description: "".to_string(),
636 priority: None,
637 labels: None,
638 blocks: Some(vec![blocker1.clone()]),
639 ..Default::default()
640 },
641 None,
642 )
643 .await
644 .unwrap();
645
646 let update = TaskUpdate {
648 add_blocks: Some(vec![blocker2.clone()]),
649 remove_blocks: Some(vec![blocker1.clone()]),
650 ..Default::default()
651 };
652
653 let updated = store.update(&task.id, update, None).await.unwrap();
654
655 assert_eq!(updated.blocks.len(), 1);
657 assert!(!updated.blocks.contains(&blocker1));
658 assert!(updated.blocks.contains(&blocker2));
659 }
660
661 #[tokio::test]
662 async fn test_memory_store_with_tasks() {
663 let existing_tasks = vec![
664 Task {
665 id: TaskId::from_string("task-1"),
666 subject: "Existing task 1".to_string(),
667 description: "".to_string(),
668 status: TaskStatus::Completed,
669 priority: TaskPriority::High,
670 labels: vec![],
671 blocks: vec![],
672 owner: None,
673 metadata: std::collections::HashMap::new(),
674 blocked_by: vec![],
675 created_at: "2025-01-01T00:00:00Z".to_string(),
676 updated_at: "2025-01-01T00:00:00Z".to_string(),
677 created_by_session: None,
678 updated_by_session: None,
679 },
680 Task {
681 id: TaskId::from_string("task-2"),
682 subject: "Existing task 2".to_string(),
683 description: "".to_string(),
684 status: TaskStatus::InProgress,
685 priority: TaskPriority::Medium,
686 labels: vec!["work".to_string()],
687 blocks: vec![],
688 owner: None,
689 metadata: std::collections::HashMap::new(),
690 blocked_by: vec![],
691 created_at: "2025-01-02T00:00:00Z".to_string(),
692 updated_at: "2025-01-02T00:00:00Z".to_string(),
693 created_by_session: Some("old-session".to_string()),
694 updated_by_session: Some("old-session".to_string()),
695 },
696 ];
697
698 let store = MemoryTaskStore::with_tasks(existing_tasks);
699
700 assert_eq!(store.len(), 2);
701 assert!(!store.is_empty());
702
703 let task1 = store
705 .get(&TaskId::from_string("task-1"))
706 .await
707 .unwrap()
708 .unwrap();
709 assert_eq!(task1.subject, "Existing task 1");
710 assert_eq!(task1.status, TaskStatus::Completed);
711
712 let task2 = store
713 .get(&TaskId::from_string("task-2"))
714 .await
715 .unwrap()
716 .unwrap();
717 assert_eq!(task2.subject, "Existing task 2");
718 assert_eq!(task2.labels, vec!["work".to_string()]);
719 }
720
721 #[tokio::test]
722 async fn test_memory_store_default() {
723 let store = MemoryTaskStore::default();
724 assert!(store.is_empty());
725 assert_eq!(store.len(), 0);
726 }
727
728 #[tokio::test]
734 async fn test_memory_store_update_owner() {
735 let store = MemoryTaskStore::new();
736
737 let task = store
738 .create(
739 NewTask {
740 subject: "Task with no owner".to_string(),
741 description: "".to_string(),
742 priority: None,
743 labels: None,
744 blocks: None,
745 ..Default::default()
746 },
747 None,
748 )
749 .await
750 .unwrap();
751
752 assert!(task.owner.is_none());
754
755 let update = TaskUpdate {
757 owner: Some("alice".to_string()),
758 ..Default::default()
759 };
760 let updated = store.update(&task.id, update, None).await.unwrap();
761 assert_eq!(updated.owner, Some("alice".to_string()));
762
763 let update = TaskUpdate {
765 owner: Some("bob".to_string()),
766 ..Default::default()
767 };
768 let updated = store.update(&task.id, update, None).await.unwrap();
769 assert_eq!(updated.owner, Some("bob".to_string()));
770
771 }
774
775 #[tokio::test]
776 async fn test_memory_store_update_metadata_merge() {
777 let store = MemoryTaskStore::new();
778
779 let task = store
780 .create(
781 NewTask {
782 subject: "Task with metadata".to_string(),
783 description: "".to_string(),
784 priority: None,
785 labels: None,
786 blocks: None,
787 ..Default::default()
788 },
789 None,
790 )
791 .await
792 .unwrap();
793
794 assert!(task.metadata.is_empty());
796
797 let mut metadata = std::collections::HashMap::new();
799 metadata.insert("key1".to_string(), serde_json::json!("value1"));
800 metadata.insert("key2".to_string(), serde_json::json!(100));
801
802 let update = TaskUpdate {
803 metadata: Some(metadata),
804 ..Default::default()
805 };
806 let updated = store.update(&task.id, update, None).await.unwrap();
807
808 assert_eq!(updated.metadata.len(), 2);
809 assert_eq!(
810 updated.metadata.get("key1"),
811 Some(&serde_json::json!("value1"))
812 );
813 assert_eq!(updated.metadata.get("key2"), Some(&serde_json::json!(100)));
814
815 let mut metadata2 = std::collections::HashMap::new();
817 metadata2.insert("key2".to_string(), serde_json::json!(200)); metadata2.insert("key3".to_string(), serde_json::json!("new")); let update = TaskUpdate {
821 metadata: Some(metadata2),
822 ..Default::default()
823 };
824 let updated = store.update(&task.id, update, None).await.unwrap();
825
826 assert_eq!(updated.metadata.len(), 3);
827 assert_eq!(
828 updated.metadata.get("key1"),
829 Some(&serde_json::json!("value1"))
830 ); assert_eq!(updated.metadata.get("key2"), Some(&serde_json::json!(200))); assert_eq!(
833 updated.metadata.get("key3"),
834 Some(&serde_json::json!("new"))
835 ); }
837
838 #[tokio::test]
839 async fn test_memory_store_update_metadata_delete_with_null() {
840 let store = MemoryTaskStore::new();
841
842 let task = store
843 .create(
844 NewTask {
845 subject: "Task with metadata to delete".to_string(),
846 description: "".to_string(),
847 priority: None,
848 labels: None,
849 blocks: None,
850 ..Default::default()
851 },
852 None,
853 )
854 .await
855 .unwrap();
856
857 let mut metadata = std::collections::HashMap::new();
859 metadata.insert("keep".to_string(), serde_json::json!("keep me"));
860 metadata.insert(
861 "delete_me".to_string(),
862 serde_json::json!("will be deleted"),
863 );
864
865 let update = TaskUpdate {
866 metadata: Some(metadata),
867 ..Default::default()
868 };
869 store.update(&task.id, update, None).await.unwrap();
870
871 let mut metadata_delete = std::collections::HashMap::new();
873 metadata_delete.insert("delete_me".to_string(), serde_json::Value::Null);
874
875 let update = TaskUpdate {
876 metadata: Some(metadata_delete),
877 ..Default::default()
878 };
879 let updated = store.update(&task.id, update, None).await.unwrap();
880
881 assert_eq!(updated.metadata.len(), 1);
883 assert_eq!(
884 updated.metadata.get("keep"),
885 Some(&serde_json::json!("keep me"))
886 );
887 assert!(!updated.metadata.contains_key("delete_me"));
888 }
889
890 #[tokio::test]
891 async fn test_memory_store_update_add_blocked_by() {
892 let store = MemoryTaskStore::new();
893
894 let blocker1 = store
896 .create(
897 NewTask {
898 subject: "Blocker 1".to_string(),
899 description: "".to_string(),
900 priority: None,
901 labels: None,
902 blocks: None,
903 ..Default::default()
904 },
905 None,
906 )
907 .await
908 .unwrap();
909
910 let blocker2 = store
911 .create(
912 NewTask {
913 subject: "Blocker 2".to_string(),
914 description: "".to_string(),
915 priority: None,
916 labels: None,
917 blocks: None,
918 ..Default::default()
919 },
920 None,
921 )
922 .await
923 .unwrap();
924
925 let task = store
927 .create(
928 NewTask {
929 subject: "Blocked task".to_string(),
930 description: "".to_string(),
931 priority: None,
932 labels: None,
933 blocks: None,
934 ..Default::default()
935 },
936 None,
937 )
938 .await
939 .unwrap();
940
941 assert!(task.blocked_by.is_empty());
943
944 let update = TaskUpdate {
946 add_blocked_by: Some(vec![blocker1.id.clone()]),
947 ..Default::default()
948 };
949 let updated = store.update(&task.id, update, None).await.unwrap();
950
951 assert_eq!(updated.blocked_by.len(), 1);
952 assert!(updated.blocked_by.contains(&blocker1.id));
953
954 let update = TaskUpdate {
956 add_blocked_by: Some(vec![blocker2.id.clone()]),
957 ..Default::default()
958 };
959 let updated = store.update(&task.id, update, None).await.unwrap();
960
961 assert_eq!(updated.blocked_by.len(), 2);
962 assert!(updated.blocked_by.contains(&blocker1.id));
963 assert!(updated.blocked_by.contains(&blocker2.id));
964
965 let update = TaskUpdate {
967 add_blocked_by: Some(vec![blocker1.id.clone()]),
968 ..Default::default()
969 };
970 let updated = store.update(&task.id, update, None).await.unwrap();
971
972 assert_eq!(updated.blocked_by.len(), 2); }
974
975 #[tokio::test]
976 async fn test_memory_store_update_remove_blocked_by() {
977 let store = MemoryTaskStore::new();
978
979 let blocker1 = TaskId::from_string("blocker-1");
980 let blocker2 = TaskId::from_string("blocker-2");
981 let blocker3 = TaskId::from_string("blocker-3");
982
983 let task = store
986 .create(
987 NewTask {
988 subject: "Task blocked by multiple".to_string(),
989 description: "".to_string(),
990 priority: None,
991 labels: None,
992 blocks: None,
993 ..Default::default()
994 },
995 None,
996 )
997 .await
998 .unwrap();
999
1000 let update = TaskUpdate {
1002 add_blocked_by: Some(vec![blocker1.clone(), blocker2.clone(), blocker3.clone()]),
1003 ..Default::default()
1004 };
1005 let task = store.update(&task.id, update, None).await.unwrap();
1006 assert_eq!(task.blocked_by.len(), 3);
1007
1008 let update = TaskUpdate {
1010 remove_blocked_by: Some(vec![blocker2.clone()]),
1011 ..Default::default()
1012 };
1013 let updated = store.update(&task.id, update, None).await.unwrap();
1014
1015 assert_eq!(updated.blocked_by.len(), 2);
1016 assert!(updated.blocked_by.contains(&blocker1));
1017 assert!(!updated.blocked_by.contains(&blocker2));
1018 assert!(updated.blocked_by.contains(&blocker3));
1019
1020 let update = TaskUpdate {
1022 remove_blocked_by: Some(vec![blocker1.clone(), blocker3.clone()]),
1023 ..Default::default()
1024 };
1025 let updated = store.update(&task.id, update, None).await.unwrap();
1026
1027 assert!(updated.blocked_by.is_empty());
1028 }
1029
1030 #[tokio::test]
1031 async fn test_memory_store_update_add_and_remove_blocked_by_same_update() {
1032 let store = MemoryTaskStore::new();
1033
1034 let blocker1 = TaskId::from_string("blocker-1");
1035 let blocker2 = TaskId::from_string("blocker-2");
1036
1037 let task = store
1038 .create(
1039 NewTask {
1040 subject: "Task".to_string(),
1041 description: "".to_string(),
1042 priority: None,
1043 labels: None,
1044 blocks: None,
1045 ..Default::default()
1046 },
1047 None,
1048 )
1049 .await
1050 .unwrap();
1051
1052 let update = TaskUpdate {
1054 add_blocked_by: Some(vec![blocker1.clone()]),
1055 ..Default::default()
1056 };
1057 store.update(&task.id, update, None).await.unwrap();
1058
1059 let update = TaskUpdate {
1061 add_blocked_by: Some(vec![blocker2.clone()]),
1062 remove_blocked_by: Some(vec![blocker1.clone()]),
1063 ..Default::default()
1064 };
1065 let updated = store.update(&task.id, update, None).await.unwrap();
1066
1067 assert_eq!(updated.blocked_by.len(), 1);
1069 assert!(!updated.blocked_by.contains(&blocker1));
1070 assert!(updated.blocked_by.contains(&blocker2));
1071 }
1072}