Skip to main content

ito_core/
backend_task_repository.rs

1//! Backend-backed task repository adapter.
2//!
3//! Delegates task reads to a [`BackendTaskReader`] when backend mode is
4//! enabled. Falls back to empty results when the backend has no tasks
5//! artifact for a change.
6
7use ito_domain::backend::BackendTaskReader;
8use ito_domain::errors::DomainResult;
9use ito_domain::tasks::{TaskRepository as DomainTaskRepository, TasksParseResult};
10
11/// Backend-backed task repository.
12///
13/// Wraps a [`BackendTaskReader`] and parses the tasks markdown content
14/// returned by the backend using the standard task parser.
15pub struct BackendTaskRepository<R: BackendTaskReader> {
16    reader: R,
17}
18
19impl<R: BackendTaskReader> BackendTaskRepository<R> {
20    /// Create a backend-backed task repository.
21    pub fn new(reader: R) -> Self {
22        Self { reader }
23    }
24}
25
26impl<R: BackendTaskReader> DomainTaskRepository for BackendTaskRepository<R> {
27    fn load_tasks(&self, change_id: &str) -> DomainResult<TasksParseResult> {
28        let content = self.reader.load_tasks_content(change_id)?;
29        let Some(content) = content else {
30            return Ok(TasksParseResult::empty());
31        };
32
33        Ok(ito_domain::tasks::parse_tasks_tracking_file(&content))
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use ito_domain::errors::DomainResult;
41
42    /// In-memory backend task reader for tests.
43    struct FakeTaskReader {
44        content: Option<String>,
45    }
46
47    impl FakeTaskReader {
48        fn with_content(content: &str) -> Self {
49            Self {
50                content: Some(content.to_string()),
51            }
52        }
53
54        fn empty() -> Self {
55            Self { content: None }
56        }
57    }
58
59    impl BackendTaskReader for FakeTaskReader {
60        fn load_tasks_content(&self, _change_id: &str) -> DomainResult<Option<String>> {
61            Ok(self.content.clone())
62        }
63    }
64
65    #[test]
66    fn missing_tasks_returns_empty() {
67        let reader = FakeTaskReader::empty();
68        let repo = BackendTaskRepository::new(reader);
69
70        let result = repo.load_tasks("test-change").unwrap();
71        assert_eq!(result.progress.total, 0);
72        assert_eq!(result.progress.complete, 0);
73    }
74
75    #[test]
76    fn checkbox_tasks_parsed_correctly() {
77        let reader = FakeTaskReader::with_content("# Tasks\n- [x] Done\n- [ ] Pending\n");
78        let repo = BackendTaskRepository::new(reader);
79
80        let result = repo.load_tasks("test-change").unwrap();
81        assert_eq!(result.progress.total, 2);
82        assert_eq!(result.progress.complete, 1);
83    }
84
85    #[test]
86    fn get_task_counts_from_backend() {
87        let reader = FakeTaskReader::with_content("# Tasks\n- [x] A\n- [x] B\n- [ ] C\n");
88        let repo = BackendTaskRepository::new(reader);
89
90        let (completed, total) = repo.get_task_counts("test-change").unwrap();
91        assert_eq!(completed, 2);
92        assert_eq!(total, 3);
93    }
94
95    #[test]
96    fn has_tasks_detects_content() {
97        let reader = FakeTaskReader::with_content("# Tasks\n- [ ] A\n");
98        let repo = BackendTaskRepository::new(reader);
99
100        assert!(repo.has_tasks("test-change").unwrap());
101    }
102
103    #[test]
104    fn has_tasks_empty_content() {
105        let reader = FakeTaskReader::empty();
106        let repo = BackendTaskRepository::new(reader);
107
108        assert!(!repo.has_tasks("test-change").unwrap());
109    }
110}