AtlatestRepositoryfolio

folio / tree / testtest-task-id-stability.sgl

1;;; Task ids survive a move, in every direction.
2;;;
3;;; Ids are globally unique random hex (`generate-task-id`) with no per-project
4;;; id space, so nothing ever required a new one on move. The old behaviour was
5;;; an oversight in two halves: `project-add-task!` called `generate-task-id`
6;;; unconditionally and ignored any id handed to it, and `move-task-in-store!`
7;;; stripped the id out of the metadata on the way in.
8;;;
9;;; The asymmetry is what marked it as an oversight rather than a decision:
10;;; project -> inbox passed metadata through untouched, and only
11;;; inbox -> project destroyed the id. But project -> inbox was not clean
12;;; either: `inbox-add!` prepended a fresh id and left the old one behind it in
13;;; the alist, so the line ended up carrying TWO `id` entries.
15(import (sigil test)
16 (sigil struct)
17 (sigil string)
18 (sigil dict)
19 (sigil fs)
20 (folio store)
21 (folio task)
22 (folio id)
23 (folio inbox)
24 (folio project)
25 (folio tools))
27;; How many times `key` appears in a task's metadata alist.
28(define (meta-key-count task key)
29 (length (filter (lambda (p) (eq? (car p) key))
30 (folio-task-metadata task))))
32(test-group "task-move preserves ids"
33 (test "positive control: the move lands the task in the target project"
34 (call-with-temp-directory
35 (lambda (dir)
36 (let* ((store (make-folio-store dir))
37 (file (project-create! store "Target" "Goal" #f))
38 (added (inbox-add! store "Route me" '())))
39 (let ((result (move-task-in-store! store (folio-task-id added) "target")))
40 (assert-true (string-contains? result "Moved")))
41 (assert-equal 0 (length (read-inbox store)))
42 (assert-equal 1 (length (folio-project-tasks (read-project file))))))))
44 (test "inbox -> project keeps the id"
45 (call-with-temp-directory
46 (lambda (dir)
47 (let* ((store (make-folio-store dir))
48 (file (project-create! store "Target" "Goal" #f))
49 (added (inbox-add! store "Route me" '((priority . "high"))))
50 (id (folio-task-id added)))
51 (move-task-in-store! store id "target")
52 (let ((moved (car (folio-project-tasks (read-project file)))))
53 (assert-equal id (folio-task-id moved))
54 (assert-equal "high" (task-priority moved))
55 (assert-equal 1 (meta-key-count moved 'id)))))))
57 (test "project -> inbox keeps the id and emits exactly one"
58 (call-with-temp-directory
59 (lambda (dir)
60 (let* ((store (make-folio-store dir))
61 (file (project-create! store "Source" "Goal" #f))
62 (added (project-add-task! file "Send me back" '((priority . "low"))))
63 (id (folio-task-id added)))
64 (move-task-in-store! store id #f)
65 (let ((moved (car (read-inbox store))))
66 (assert-equal id (folio-task-id moved))
67 (assert-equal "low" (task-priority moved))
68 (assert-equal 1 (meta-key-count moved 'id)))))))
70 (test "project -> project keeps the id"
71 (call-with-temp-directory
72 (lambda (dir)
73 (let* ((store (make-folio-store dir))
74 (src (project-create! store "Source" "Goal" #f))
75 (dst (project-create! store "Dest" "Goal" #f))
76 (added (project-add-task! src "Relocate me" '()))
77 (id (folio-task-id added)))
78 (move-task-in-store! store id "dest")
79 (let ((moved (car (folio-project-tasks (read-project dst)))))
80 (assert-equal id (folio-task-id moved)))))))
82 (test "a round trip out and back returns the same id"
83 (call-with-temp-directory
84 (lambda (dir)
85 (let* ((store (make-folio-store dir))
86 (file (project-create! store "Target" "Goal" #f))
87 (added (inbox-add! store "Out and back" '()))
88 (id (folio-task-id added)))
89 (move-task-in-store! store id "target")
90 (move-task-in-store! store id #f)
91 (let ((back (car (read-inbox store))))
92 (assert-equal id (folio-task-id back))
93 (assert-equal 1 (meta-key-count back 'id)))))))
95 (test "inbox-triage keeps the id"
96 (call-with-temp-directory
97 (lambda (dir)
98 (let* ((store (make-folio-store dir))
99 (file (project-create! store "Target" "Goal" #f))
100 (added (inbox-add! store "Triage me" '()))
101 (id (folio-task-id added))
102 (handler (make-tool-inbox-triage store)))
103 (handler (dict id: id project: "target"))
104 (let ((moved (car (folio-project-tasks (read-project file)))))
105 (assert-equal id (folio-task-id moved))
106 (assert-equal 1 (meta-key-count moved 'id)))))))
108 ;; The whole point of stable ids: a held reference stays valid.
109 (test "task-update on the pre-move id still resolves after the move"
110 (call-with-temp-directory
111 (lambda (dir)
112 (let* ((store (make-folio-store dir))
113 (file (project-create! store "Target" "Goal" #f))
114 (added (inbox-add! store "Chase me" '()))
115 (id (folio-task-id added)))
116 (move-task-in-store! store id "target")
117 (let ((result (update-task-in-store! store id (dict priority: "high"))))
118 (assert-true (string-contains? result "Updated")))
119 (assert-equal "high"
120 (task-priority
121 (car (folio-project-tasks (read-project file))))))))))
123(test-group "project-add-task! id handling"
124 (test "honours a supplied id"
125 (call-with-temp-directory
126 (lambda (dir)
127 (let* ((store (make-folio-store dir))
128 (file (project-create! store "Target" "Goal" #f))
129 (task (project-add-task! file "Preassigned"
130 '((id . "t-abcd") (priority . "high")))))
131 (assert-equal "t-abcd" (folio-task-id task))
132 (assert-equal 1 (meta-key-count task 'id))
133 (let ((read-back (car (folio-project-tasks (read-project file)))))
134 (assert-equal "t-abcd" (folio-task-id read-back))
135 (assert-equal "high" (task-priority read-back)))))))
137 (test "still generates an id when none is supplied"
138 (call-with-temp-directory
139 (lambda (dir)
140 (let* ((store (make-folio-store dir))
141 (file (project-create! store "Target" "Goal" #f))
142 (task (project-add-task! file "Fresh" '())))
143 (assert-true (string-starts-with? (folio-task-id task) "t-"))
144 (assert-equal 1 (meta-key-count task 'id)))))))
146(test-group "inbox-add! id handling"
147 (test "honours a supplied id"
148 (call-with-temp-directory
149 (lambda (dir)
150 (let* ((store (make-folio-store dir))
151 (task (inbox-add! store "Preassigned" '((id . "t-beef")))))
152 (assert-equal "t-beef" (folio-task-id task))
153 (assert-equal 1 (meta-key-count task 'id))
154 (assert-equal "t-beef" (folio-task-id (car (read-inbox store))))))))
156 (test "still generates an id when none is supplied"
157 (call-with-temp-directory
158 (lambda (dir)
159 (let* ((store (make-folio-store dir))
160 (task (inbox-add! store "Fresh" '())))
161 (assert-true (string-starts-with? (folio-task-id task) "t-"))
162 (assert-equal 1 (meta-key-count task 'id)))))))