208
208
with
209
209
| Some r -> Evidence.Workout.filled_slots r.Repository.workout
210
210
| None -> Alcotest.failf "expected the saved workout w1") );
211
-
Removed:
( "sequential feedback stores only answered factors, never skipped ones",
211
+
Added:
( "the feedback modal shows every factor with three choices, none \
212
+
Added:
preselected, and one submit",
212
213
`Quick,
213
214
fun () ->
214
-
Removed:
let c, repo = client_with_repo () in
215
+
Added:
let c, _ = client_with_repo () in
215
216
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
216
-
Removed:
let trainee = the_trainee repo ~username:"lifter" in
217
-
Removed:
let _ = get c "/logbook?feedback=start" in
218
-
Removed:
let step_action step action extra =
219
-
Removed:
let token = Option.get (csrf_token (body (get c "/logbook"))) in
220
-
Removed:
post c "/feedback"
221
-
Removed:
(("dream.csrf", token)
222
-
Removed:
:: ("step", string_of_int step)
223
-
Removed:
:: ("action", action) :: extra)
217
+
Added:
let page = body (get c "/logbook?feedback=start") in
218
+
Added:
let count needle =
219
+
Added:
let rec go from acc =
220
+
Added:
match index_from ~needle page from with
221
+
Added:
| Some i -> go (i + 1) (acc + 1)
222
+
Added:
| None -> acc
223
+
Added:
in
224
+
Added:
go 0 0
224
225
in
225
-
Removed:
let first = step_action 0 "next" [ ("choice", "better") ] in
226
-
Removed:
Alcotest.(check int) "first factor redirects" 303 (status first);
227
-
Removed:
let _ = step_action 1 "skip" [] in
228
-
Removed:
let saved = step_action 2 "save" [] in
229
-
Removed:
Alcotest.(check int) "final feedback redirects" 303 (status saved);
230
-
Removed:
let reports =
231
-
Removed:
Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)
232
-
Removed:
in
226
+
Added:
(* One radio group per factor, three values each. *)
233
227
Alcotest.(check int)
234
-
Removed:
"one feedback report stored" 1 (List.length reports);
235
-
Removed:
let signal_list = Feedback.signals (List.hd reports) in
236
-
Removed:
Alcotest.(check (list bool))
237
-
Removed:
"only motivation stored" [ true ]
238
-
Removed:
(List.map
239
-
Removed:
(function
240
-
Removed:
| Feedback.Motivation Feedback.Better_than_usual -> true
241
-
Removed:
| _ -> false)
242
-
Removed:
signal_list) );
243
-
Removed:
( "skipping the entire feedback flow records nothing",
244
-
Removed:
`Quick,
245
-
Removed:
fun () ->
246
-
Removed:
let c, repo = client_with_repo () in
247
-
Removed:
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
248
-
Removed:
let trainee = the_trainee repo ~username:"lifter" in
249
-
Removed:
let start = body (get c "/logbook?feedback=start") in
250
-
Removed:
let token = Option.get (csrf_token start) in
251
-
Removed:
let skipped = post c "/feedback/cancel" [ ("dream.csrf", token) ] in
252
-
Removed:
Alcotest.(check int) "full-flow skip redirects" 303 (status skipped);
228
+
Added:
"motivation has three choices" 3
229
+
Added:
(count "name=\"motivation\"");
253
230
Alcotest.(check int)
254
-
Removed:
"no feedback report was stored" 0
255
-
Removed:
(List.length
256
-
Removed:
(Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)))
231
+
Added:
"difficulty has three choices" 3
232
+
Added:
(count "name=\"difficulty\"");
233
+
Added:
List.iter
234
+
Added:
(fun v ->
235
+
Added:
Alcotest.(check bool)
236
+
Added:
("offers " ^ v) true
237
+
Added:
(count (Printf.sprintf "value=\"%s\"" v) >= 2))
238
+
Added:
[ "worse"; "usual"; "better" ];
239
+
Added:
(* Nothing is preselected and there is no step machinery. *)
240
+
Added:
Alcotest.(check int) "no checked radio" 0 (count " checked");
241
+
Added:
Alcotest.(check int) "no step field" 0 (count "name=\"step\"");
242
+
Added:
Alcotest.(check int) "one submit" 1 (count ">Submit feedback<");
243
+
Added:
(* Close is a plain link back to the logbook, not a cancel post. *)
244
+
Added:
Alcotest.(check bool)
245
+
Added:
"close links to the logbook" true
246
+
Added:
(count "href=\"/logbook\"" >= 1);
247
+
Added:
Alcotest.(check int) "no cancel route" 0 (count "/feedback/cancel")
257
248
);
258
-
Removed:
( "back keeps an earlier answer, which still persists on save",
249
+
Added:
( "submitting one factor stores only that signal",
259
250
`Quick,
260
251
fun () ->
261
252
let c, repo = client_with_repo () in
262
253
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
263
254
let trainee = the_trainee repo ~username:"lifter" in
264
-
Removed:
let _ = get c "/logbook?feedback=start" in
265
-
Removed:
let step_action step action extra =
266
-
Removed:
let token = Option.get (csrf_token (body (get c "/logbook"))) in
255
+
Added:
let token = Option.get (csrf_token (body (get c "/logbook"))) in
256
+
Added:
let saved =
267
257
post c "/feedback"
268
-
Removed:
(("dream.csrf", token)
269
-
Removed:
:: ("step", string_of_int step)
270
-
Removed:
:: ("action", action) :: extra)
258
+
Added:
[ ("dream.csrf", token); ("motivation", "better") ]
271
259
in
272
-
Removed:
(* Answer motivation, move to difficulty, return, then skip
273
-
Removed:
motivation and difficulty before saving. The earlier motivation
274
-
Removed:
answer remains in the flow. *)
275
-
Removed:
let _ = step_action 0 "next" [ ("choice", "better") ] in
276
-
Removed:
let back = step_action 1 "back" [] in
277
-
Removed:
Alcotest.(check int) "back redirects" 303 (status back);
278
-
Removed:
let _ = step_action 0 "skip" [] in
279
-
Removed:
let _ = step_action 1 "skip" [] in
280
-
Removed:
let _ = step_action 2 "save" [] in
260
+
Added:
Alcotest.(check int) "submit redirects" 303 (status saved);
261
+
Added:
Alcotest.(check string)
262
+
Added:
"back to the logbook" "/logbook" (location saved);
281
263
let reports =
282
264
Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)
283
265
in
284
266
Alcotest.(check int) "one report stored" 1 (List.length reports);
285
-
Removed:
let signals = Feedback.signals (List.hd reports) in
286
267
Alcotest.(check bool)
287
-
Removed:
"the pre-back motivation answer persists" true
288
-
Removed:
(List.exists
289
-
Removed:
(function
290
-
Removed:
| Feedback.Motivation Feedback.Better_than_usual -> true
291
-
Removed:
| _ -> false)
292
-
Removed:
signals) );
293
-
Removed:
( "closing the feedback modal discards the flow",
268
+
Added:
"only motivation, better than usual" true
269
+
Added:
(Feedback.signals (List.hd reports)
270
+
Added:
= [ Feedback.Motivation Feedback.Better_than_usual ]) );
271
+
Added:
( "submitting every factor stores exactly one signal per category",
294
272
`Quick,
295
273
fun () ->
296
274
let c, repo = client_with_repo () in
297
275
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
298
276
let trainee = the_trainee repo ~username:"lifter" in
299
-
Removed:
let start = body (get c "/logbook?feedback=start") in
300
-
Removed:
let token = Option.get (csrf_token start) in
301
-
Removed:
let _ =
277
+
Added:
let token = Option.get (csrf_token (body (get c "/logbook"))) in
278
+
Added:
let saved =
302
279
post c "/feedback"
303
280
[
304
281
("dream.csrf", token);
305
-
Removed:
("step", "0");
306
-
Removed:
("action", "next");
307
-
Removed:
("choice", "better");
282
+
Added:
("motivation", "worse");
283
+
Added:
("difficulty", "usual");
308
284
]
309
285
in
310
-
Removed:
let appetite = body (get c "/logbook") in
311
-
Removed:
let token = Option.get (csrf_token appetite) in
312
-
Removed:
let closed = post c "/feedback/cancel" [ ("dream.csrf", token) ] in
313
-
Removed:
Alcotest.(check int) "close redirects" 303 (status closed);
314
-
Removed:
(* A discarded flow stores nothing. *)
286
+
Added:
Alcotest.(check int) "submit redirects" 303 (status saved);
287
+
Added:
let reports =
288
+
Added:
Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)
289
+
Added:
in
290
+
Added:
Alcotest.(check int) "one report stored" 1 (List.length reports);
291
+
Added:
let signals = Feedback.signals (List.hd reports) in
292
+
Added:
let count p = List.length (List.filter p signals) in
293
+
Added:
let open Feedback in
315
294
Alcotest.(check int)
316
-
Removed:
"no feedback report stored" 0
317
-
Removed:
(List.length
318
-
Removed:
(Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)))
319
-
Removed:
);
320
-
Removed:
( "a step out of sync with the session records nothing",
295
+
Added:
"one motivation signal" 1
296
+
Added:
(count (function Motivation _ -> true | _ -> false));
297
+
Added:
Alcotest.(check int)
298
+
Added:
"one difficulty signal" 1
299
+
Added:
(count (function Difficulty _ -> true | _ -> false)) );
300
+
Added:
( "submitting with nothing selected stores nothing",
321
301
`Quick,
322
302
fun () ->
323
-
Removed:
(* The flow advances one factor at a time and stores its position in
324
-
Removed:
the session. A post whose [step] disagrees with that position —
325
-
Removed:
a stale form, a double submit, or a crafted body — is refused as
326
-
Removed:
an invalid form rather than recording a factor twice. This is the
327
-
Removed:
web tier's guard against a duplicate signal ever reaching the
328
-
Removed:
domain. *)
329
303
let c, repo = client_with_repo () in
330
304
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
331
305
let trainee = the_trainee repo ~username:"lifter" in
332
-
Removed:
let start = body (get c "/logbook?feedback=start") in
333
-
Removed:
let token = Option.get (csrf_token start) in
334
-
Removed:
(* The session is at step 0; post step 3 instead. *)
335
-
Removed:
let mismatched =
336
-
Removed:
post c "/feedback"
337
-
Removed:
[
338
-
Removed:
("dream.csrf", token);
339
-
Removed:
("step", "3");
340
-
Removed:
("action", "next");
341
-
Removed:
("choice", "1");
342
-
Removed:
]
343
-
Removed:
in
344
-
Removed:
Alcotest.(check int)
345
-
Removed:
"mismatched step redirects" 303 (status mismatched);
306
+
Added:
let token = Option.get (csrf_token (body (get c "/logbook"))) in
307
+
Added:
let empty = post c "/feedback" [ ("dream.csrf", token) ] in
308
+
Added:
Alcotest.(check int) "empty submit redirects" 303 (status empty);
346
309
Alcotest.(check string)
347
-
Removed:
"stale feedback returns to the logbook" "/logbook"
348
-
Removed:
(location mismatched);
349
-
Removed:
(* The mismatched post advanced nothing and stored no report. *)
310
+
Added:
"back to the logbook" "/logbook" (location empty);
350
311
Alcotest.(check int)
351
-
Removed:
"no feedback stored" 0
312
+
Added:
"no report stored" 0
352
313
(List.length
353
314
(Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)))
354
315
);
355
-
Removed:
( "answering every factor stores exactly one signal per category",
316
+
Added:
( "a value outside the three choices is ignored",
356
317
`Quick,
357
318
fun () ->
358
-
Removed:
(* The flow admits one answer per factor, so the stored report holds
359
-
Removed:
one signal per category and never a duplicate. *)
319
+
Added:
(* A crafted or stale value for a factor is treated as unselected,
320
+
Added:
so it can neither store a bad level nor reach the domain. *)
360
321
let c, repo = client_with_repo () in
361
322
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
362
323
let trainee = the_trainee repo ~username:"lifter" in
363
-
Removed:
let _ = get c "/logbook?feedback=start" in
364
-
Removed:
let rec advance step =
365
-
Removed:
if step < 2 then begin
366
-
Removed:
let token = Option.get (csrf_token (body (get c "/logbook"))) in
367
-
Removed:
let _ =
368
-
Removed:
post c "/feedback"
369
-
Removed:
[
370
-
Removed:
("dream.csrf", token);
371
-
Removed:
("step", string_of_int step);
372
-
Removed:
("action", "next");
373
-
Removed:
("choice", "worse");
374
-
Removed:
]
375
-
Removed:
in
376
-
Removed:
advance (step + 1)
377
-
Removed:
end
378
-
Removed:
in
379
-
Removed:
advance 0;
380
324
let token = Option.get (csrf_token (body (get c "/logbook"))) in
381
-
Removed:
let saved =
325
+
Added:
let bad =
382
326
post c "/feedback"
383
-
Removed:
[ ("dream.csrf", token); ("step", "2"); ("action", "save") ]
327
+
Added:
[
328
+
Added:
("dream.csrf", token);
329
+
Added:
("motivation", "7");
330
+
Added:
("difficulty", "better");
331
+
Added:
]
384
332
in
385
-
Removed:
Alcotest.(check int) "final save redirects" 303 (status saved);
386
-
Removed:
(* Exactly one report, with one signal per distinct factor. *)
333
+
Added:
Alcotest.(check int) "submit redirects" 303 (status bad);
387
334
let reports =
388
335
Lwt_main.run (Memory_repo.feedback repo trainee.Trainee.id)
389
336
in
390
337
Alcotest.(check int) "one report stored" 1 (List.length reports);
391
-
Removed:
let signals = Feedback.signals (List.hd reports) in
392
-
Removed:
let count p = List.length (List.filter p signals) in
393
-
Removed:
let open Feedback in
394
-
Removed:
Alcotest.(check int)
395
-
Removed:
"one motivation signal" 1
396
-
Removed:
(count (function Motivation _ -> true | _ -> false));
397
-
Removed:
Alcotest.(check int)
398
-
Removed:
"one difficulty signal" 1
399
-
Removed:
(count (function Difficulty _ -> true | _ -> false)) );
338
+
Added:
Alcotest.(check bool)
339
+
Added:
"only the valid factor survives" true
340
+
Added:
(Feedback.signals (List.hd reports)
341
+
Added:
= [ Feedback.Difficulty Feedback.Better_than_usual ]) );
400
342
] );
401
343
]