AtlatestRepositoryfolio

folio / tree / testtest-batch-operations.sgl

1(import (sigil test)
2 (sigil struct)
3 (sigil string)
4 (sigil dict)
5 (sigil fs)
6 (sigil path)
7 (folio store)
8 (folio task)
9 (folio id)
10 (folio inbox)
11 (folio project)
12 (folio tools))
14;; ============================================================
15;; inbox-sweep!
16;; ============================================================
18(test-group "inbox-sweep!"
19 (test "sweeps completed items"
20 (call-with-temp-directory
21 (lambda (dir)
22 (let ((store (make-folio-store dir)))
23 (inbox-add! store "Open task" '())
24 (inbox-add! store "Done task" '())
25 ;; Complete the second task
26 (let* ((tasks (read-inbox store))
27 (done-id (folio-task-id (cadr tasks))))
28 (complete-task-in-store! store done-id)
29 (let ((swept (inbox-sweep! store)))
30 (assert-equal 1 (length swept))
31 (assert-equal "Done task" (folio-task-text (car swept)))
32 ;; Only open task remains
33 (assert-equal 1 (length (read-inbox store)))))))))
35 (test "returns empty list when nothing to sweep"
36 (call-with-temp-directory
37 (lambda (dir)
38 (let ((store (make-folio-store dir)))
39 (inbox-add! store "Open task" '())
40 (let ((swept (inbox-sweep! store)))
41 (assert-equal 0 (length swept))
42 (assert-equal 1 (length (read-inbox store))))))))
44 (test "sweeps multiple completed items"
45 (call-with-temp-directory
46 (lambda (dir)
47 (let ((store (make-folio-store dir)))
48 (inbox-add! store "Task 1" '())
49 (inbox-add! store "Task 2" '())
50 (inbox-add! store "Task 3" '())
51 (let* ((tasks (read-inbox store))
52 (id1 (folio-task-id (car tasks)))
53 (id2 (folio-task-id (cadr tasks))))
54 (complete-task-in-store! store id1)
55 (complete-task-in-store! store id2)
56 (let ((swept (inbox-sweep! store)))
57 (assert-equal 2 (length swept))
58 (assert-equal 1 (length (read-inbox store))))))))))
60;; ============================================================
61;; task-complete-batch (via complete-task-in-store!)
62;; ============================================================
64(test-group "task-complete-batch"
65 (test "completes multiple tasks"
66 (call-with-temp-directory
67 (lambda (dir)
68 (let ((store (make-folio-store dir)))
69 (inbox-add! store "Task A" '())
70 (inbox-add! store "Task B" '())
71 (inbox-add! store "Task C" '())
72 (let* ((tasks (read-inbox store))
73 (id-a (folio-task-id (car tasks)))
74 (id-b (folio-task-id (cadr tasks))))
75 ;; Complete tasks A and B in batch
76 (complete-task-in-store! store id-a)
77 (complete-task-in-store! store id-b)
78 (let ((after (read-inbox store)))
79 (assert-equal 3 (length after))
80 ;; First two should be done
81 (assert-true (folio-task-done? (car after)))
82 (assert-true (folio-task-done? (cadr after)))
83 ;; Third still open
84 (assert-false (folio-task-done? (caddr after)))))))))
86 (test "handles invalid IDs gracefully"
87 (call-with-temp-directory
88 (lambda (dir)
89 (let ((store (make-folio-store dir)))
90 (inbox-add! store "Task A" '())
91 (let ((result (complete-task-in-store! store "t-xxxx")))
92 (assert-true (string-contains? result "not found"))))))))
94;; ============================================================
95;; task-sweep (via sweep-completed-from-file!)
96;; ============================================================
98(test-group "task-sweep"
99 (test "sweeps completed tasks from project"
100 (call-with-temp-directory
101 (lambda (dir)
102 (let* ((store (make-folio-store dir))
103 (file (project-create! store "Test" "Goal" #f)))
104 (project-add-task! file "Open task" '())
105 (project-add-task! file "Done task" '())
106 (let* ((proj (read-project file))
107 (tasks (folio-project-tasks proj))
108 (done-id (folio-task-id (cadr tasks))))
109 (complete-task-in-store! store done-id)
110 ;; Now use the MCP tool handler
111 (let* ((handler (make-tool-task-sweep store))
112 (result (handler (dict project: "test"))))
113 (assert-true (string-contains? result "Swept 1"))
114 ;; Only open task remains
115 (let ((after (folio-project-tasks (read-project file))))
116 (assert-equal 1 (length after))
117 (assert-false (folio-task-done? (car after))))))))))
119 (test "sweeps all projects when no project specified"
120 (call-with-temp-directory
121 (lambda (dir)
122 (let* ((store (make-folio-store dir))
123 (file1 (project-create! store "Project1" "Goal1" #f))
124 (file2 (project-create! store "Project2" "Goal2" #f)))
125 (project-add-task! file1 "Open 1" '())
126 (project-add-task! file1 "Done 1" '())
127 (project-add-task! file2 "Done 2" '())
128 ;; Complete the "Done" tasks
129 (let* ((proj1 (read-project file1))
130 (done1-id (folio-task-id (cadr (folio-project-tasks proj1))))
131 (proj2 (read-project file2))
132 (done2-id (folio-task-id (car (folio-project-tasks proj2)))))
133 (complete-task-in-store! store done1-id)
134 (complete-task-in-store! store done2-id)
135 (let* ((handler (make-tool-task-sweep store))
136 (result (handler (dict))))
137 (assert-true (string-contains? result "Swept 2"))))))))
139 (test "respects --before date filter"
140 (call-with-temp-directory
141 (lambda (dir)
142 (let* ((store (make-folio-store dir))
143 (file (project-create! store "Test" "Goal" #f)))
144 ;; Manually write a project with specific completed dates
145 (let ((content (string-append
146 "---\nstatus: active\ngoal: Goal\nlast-updated: 2026-04-01\n---\n\n"
147 "# Test\n\n## Tasks\n\n"
148 "- [x] Old task {id: t-0001, completed: 2026-03-01}\n"
149 "- [x] Recent task {id: t-0002, completed: 2026-04-01}\n"
150 "- [ ] Open task {id: t-0003}\n"
151 "\n## Log\n")))
152 (write-file-string file content))
153 (let* ((handler (make-tool-task-sweep store))
154 (result (handler (dict before: "2026-03-15"))))
155 ;; Only the old task (completed 2026-03-01) should be swept
156 (assert-true (string-contains? result "Swept 1"))
157 (let ((after (folio-project-tasks (read-project file))))
158 ;; Recent completed + open remain
159 (assert-equal 2 (length after))))))))
161 (test "returns message when nothing to sweep"
162 (call-with-temp-directory
163 (lambda (dir)
164 (let* ((store (make-folio-store dir))
165 (file (project-create! store "Test" "Goal" #f)))
166 (project-add-task! file "Open task" '())
167 (let* ((handler (make-tool-task-sweep store))
168 (result (handler (dict))))
169 (assert-true (string-contains? result "No completed tasks")))))))
171 (test "returns error for nonexistent project"
172 (call-with-temp-directory
173 (lambda (dir)
174 (let* ((store (make-folio-store dir))
175 (handler (make-tool-task-sweep store))
176 (result (handler (dict project: "nonexistent"))))
177 (assert-true (string-contains? result "not found")))))))
179;; ============================================================
180;; inbox-sweep MCP handler
181;; ============================================================
183(test-group "inbox-sweep handler"
184 (test "MCP handler returns summary"
185 (call-with-temp-directory
186 (lambda (dir)
187 (let ((store (make-folio-store dir)))
188 (inbox-add! store "Done item" '())
189 (let* ((tasks (read-inbox store))
190 (id (folio-task-id (car tasks))))
191 (complete-task-in-store! store id)
192 (let* ((handler (make-tool-inbox-sweep store))
193 (result (handler (dict))))
194 (assert-true (string-contains? result "Swept 1"))))))))
196 (test "MCP handler returns message when empty"
197 (call-with-temp-directory
198 (lambda (dir)
199 (let ((store (make-folio-store dir)))
200 (inbox-add! store "Open item" '())
201 (let* ((handler (make-tool-inbox-sweep store))
202 (result (handler (dict))))
203 (assert-true (string-contains? result "No completed"))))))))