AtlatestRepositoryfolio
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-directory21
(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 task26
(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 remains33
(assert-equal 1 (length (read-inbox store)))))))))35
(test "returns empty list when nothing to sweep"36
(call-with-temp-directory37
(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-directory46
(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-directory67
(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 batch76
(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 done81
(assert-true (folio-task-done? (car after)))82
(assert-true (folio-task-done? (cadr after)))83
;; Third still open84
(assert-false (folio-task-done? (caddr after)))))))))86
(test "handles invalid IDs gracefully"87
(call-with-temp-directory88
(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-directory101
(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 handler111
(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 remains115
(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-directory121
(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" tasks129
(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-directory141
(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 dates145
(let ((content (string-append146
"---\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 swept156
(assert-true (string-contains? result "Swept 1"))157
(let ((after (folio-project-tasks (read-project file))))158
;; Recent completed + open remain159
(assert-equal 2 (length after))))))))161
(test "returns message when nothing to sweep"162
(call-with-temp-directory163
(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-directory173
(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 handler181
;; ============================================================183
(test-group "inbox-sweep handler"184
(test "MCP handler returns summary"185
(call-with-temp-directory186
(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-directory198
(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"))))))))