Skip to main content

max / goingson

8.1 KB · 320 lines History Blame Raw
1 //! Integration tests for status-token methods on SqliteTaskRepository.
2
3 mod common;
4
5 use goingson_core::{
6 StatusToken, TOKEN_KIND_COMMIT, TaskAnnotations, TaskCrud, TaskId, TaskStatus, TokenState,
7 };
8 use goingson_db_sqlite::SqliteTaskRepository;
9
10 const COMMIT: &str = TOKEN_KIND_COMMIT;
11
12 #[tokio::test]
13 async fn record_token_appends_and_reads_back() {
14 let pool = common::setup_test_db().await;
15 let user_id = common::create_test_user(&pool).await;
16 let task_id = common::create_test_task(&pool, user_id).await;
17 let repo = SqliteTaskRepository::new(pool);
18
19 let t = repo
20 .record_status_token(
21 task_id,
22 user_id,
23 COMMIT,
24 "deox@7c236fca8",
25 TokenState::Pending,
26 false,
27 )
28 .await
29 .expect("record")
30 .expect("some");
31 assert_eq!(t.task_id, task_id);
32 assert_eq!(t.kind, COMMIT);
33 assert_eq!(t.reference, "deox@7c236fca8");
34 assert_eq!(t.state, TokenState::Pending);
35 assert!(!t.is_primary);
36 assert_eq!(t.position, 0);
37
38 let tokens = repo.get_status_tokens_for_task(task_id).await.expect("get");
39 assert_eq!(tokens.len(), 1);
40 assert_eq!(tokens[0].id, t.id);
41 }
42
43 #[tokio::test]
44 async fn record_token_is_idempotent_in_task_kind_ref() {
45 let pool = common::setup_test_db().await;
46 let user_id = common::create_test_user(&pool).await;
47 let task_id = common::create_test_task(&pool, user_id).await;
48 let repo = SqliteTaskRepository::new(pool);
49
50 let a = repo
51 .record_status_token(
52 task_id,
53 user_id,
54 COMMIT,
55 "deox@aaa",
56 TokenState::Pending,
57 false,
58 )
59 .await
60 .unwrap()
61 .unwrap();
62 // Re-record the same token, flipping state to Complete (e.g. it was pushed).
63 let b = repo
64 .record_status_token(
65 task_id,
66 user_id,
67 COMMIT,
68 "deox@aaa",
69 TokenState::Complete,
70 false,
71 )
72 .await
73 .unwrap()
74 .unwrap();
75 assert_eq!(
76 a.id, b.id,
77 "same (task, kind, ref) collapses to one deterministic id"
78 );
79
80 let tokens = repo.get_status_tokens_for_task(task_id).await.unwrap();
81 assert_eq!(tokens.len(), 1, "no duplicate row on re-record");
82 assert_eq!(
83 tokens[0].state,
84 TokenState::Complete,
85 "state updated in place"
86 );
87 assert_eq!(tokens[0].position, 0);
88 }
89
90 #[tokio::test]
91 async fn record_token_preserves_append_order() {
92 let pool = common::setup_test_db().await;
93 let user_id = common::create_test_user(&pool).await;
94 let task_id = common::create_test_task(&pool, user_id).await;
95 let repo = SqliteTaskRepository::new(pool);
96
97 repo.record_status_token(
98 task_id,
99 user_id,
100 COMMIT,
101 "deox@one",
102 TokenState::Pending,
103 false,
104 )
105 .await
106 .unwrap();
107 repo.record_status_token(
108 task_id,
109 user_id,
110 COMMIT,
111 "deox@two",
112 TokenState::Pending,
113 false,
114 )
115 .await
116 .unwrap();
117 repo.record_status_token(
118 task_id,
119 user_id,
120 COMMIT,
121 "deox@three",
122 TokenState::Pending,
123 false,
124 )
125 .await
126 .unwrap();
127
128 let tokens = repo.get_status_tokens_for_task(task_id).await.unwrap();
129 let refs: Vec<&str> = tokens.iter().map(|t| t.reference.as_str()).collect();
130 assert_eq!(refs, vec!["deox@one", "deox@two", "deox@three"]);
131 }
132
133 #[tokio::test]
134 async fn primary_flag_is_exclusive_and_can_promote_existing() {
135 let pool = common::setup_test_db().await;
136 let user_id = common::create_test_user(&pool).await;
137 let task_id = common::create_test_task(&pool, user_id).await;
138 let repo = SqliteTaskRepository::new(pool);
139
140 repo.record_status_token(
141 task_id,
142 user_id,
143 COMMIT,
144 "deox@one",
145 TokenState::Pending,
146 false,
147 )
148 .await
149 .unwrap();
150 repo.record_status_token(
151 task_id,
152 user_id,
153 COMMIT,
154 "deox@two",
155 TokenState::Pending,
156 false,
157 )
158 .await
159 .unwrap();
160 repo.record_status_token(
161 task_id,
162 user_id,
163 COMMIT,
164 "deox@three",
165 TokenState::Complete,
166 true,
167 )
168 .await
169 .unwrap();
170
171 let commits = repo.get_status_tokens_for_task(task_id).await.unwrap();
172 let primaries: Vec<&str> = commits
173 .iter()
174 .filter(|t| t.is_primary)
175 .map(|t| t.reference.as_str())
176 .collect();
177 assert_eq!(primaries, vec!["deox@three"], "exactly one primary");
178
179 // Promote an existing token to primary; the prior primary flag clears.
180 repo.record_status_token(
181 task_id,
182 user_id,
183 COMMIT,
184 "deox@one",
185 TokenState::Pending,
186 true,
187 )
188 .await
189 .unwrap();
190 let commits = repo.get_status_tokens_for_task(task_id).await.unwrap();
191 let primary_refs: Vec<&str> = commits
192 .iter()
193 .filter(|t| t.is_primary)
194 .map(|t| t.reference.as_str())
195 .collect();
196 assert_eq!(
197 primary_refs,
198 vec!["deox@one"],
199 "primary moved, still exactly one"
200 );
201 assert_eq!(commits.len(), 3, "promotion did not add a row");
202 }
203
204 #[tokio::test]
205 async fn delete_token_removes_only_that_token() {
206 let pool = common::setup_test_db().await;
207 let user_id = common::create_test_user(&pool).await;
208 let task_id = common::create_test_task(&pool, user_id).await;
209 let repo = SqliteTaskRepository::new(pool);
210
211 let keep = repo
212 .record_status_token(
213 task_id,
214 user_id,
215 COMMIT,
216 "deox@keep",
217 TokenState::Pending,
218 false,
219 )
220 .await
221 .unwrap()
222 .unwrap();
223 let drop = repo
224 .record_status_token(
225 task_id,
226 user_id,
227 COMMIT,
228 "deox@drop",
229 TokenState::Pending,
230 false,
231 )
232 .await
233 .unwrap()
234 .unwrap();
235
236 let removed = repo.delete_status_token(drop.id, user_id).await.unwrap();
237 assert!(removed);
238
239 let tokens = repo.get_status_tokens_for_task(task_id).await.unwrap();
240 assert_eq!(tokens.len(), 1);
241 assert_eq!(tokens[0].id, keep.id);
242
243 // Deleting an unknown token is a no-op, not an error.
244 assert!(
245 !repo
246 .delete_status_token(
247 StatusToken::deterministic_id(task_id, COMMIT, "deox@nope"),
248 user_id
249 )
250 .await
251 .unwrap()
252 );
253 }
254
255 #[tokio::test]
256 async fn record_token_rejects_unknown_or_unowned_task() {
257 let pool = common::setup_test_db().await;
258 let user_id = common::create_test_user(&pool).await;
259 let repo = SqliteTaskRepository::new(pool);
260
261 let missing = repo
262 .record_status_token(
263 TaskId::new(),
264 user_id,
265 COMMIT,
266 "deox@x",
267 TokenState::Pending,
268 false,
269 )
270 .await
271 .expect("no error");
272 assert!(
273 missing.is_none(),
274 "recording against a missing task returns None"
275 );
276 }
277
278 #[tokio::test]
279 async fn tokens_load_onto_the_task_with_rollup() {
280 let pool = common::setup_test_db().await;
281 let user_id = common::create_test_user(&pool).await;
282 let task_id = common::create_test_task(&pool, user_id).await;
283 let repo = SqliteTaskRepository::new(pool);
284
285 repo.record_status_token(
286 task_id,
287 user_id,
288 COMMIT,
289 "deox@one",
290 TokenState::Complete,
291 false,
292 )
293 .await
294 .unwrap();
295 repo.record_status_token(
296 task_id,
297 user_id,
298 COMMIT,
299 "deox@two",
300 TokenState::Pending,
301 true,
302 )
303 .await
304 .unwrap();
305
306 let task = repo.get_by_id(task_id, user_id).await.unwrap().unwrap();
307 assert_eq!(task.status_tokens.len(), 2);
308 assert_eq!(
309 task.primary_token().map(|t| t.reference.as_str()),
310 Some("deox@two")
311 );
312 assert_eq!(
313 task.status_token_summary(),
314 "pending",
315 "a pending token keeps rollup pending"
316 );
317 // status is unaffected by recording tokens
318 assert_ne!(task.status, TaskStatus::Completed);
319 }
320