open Hito_app open Support let suite = [ ( "web.profile", [ ( "changing the username updates the stored account", `Quick, fun () -> let c, repo = client_with_repo () in let _ = register c ~username:"lifter" ~password:"heavyduty1" in let trainee = the_trainee repo ~username:"lifter" in let token = Option.get (csrf_token (body (get c "/profile"))) in let changed = post c "/profile/username" [ ("dream.csrf", token); ("username", "athlete") ] in Alcotest.(check int) "redirects after the change" 303 (status changed); Alcotest.(check bool) "redirects back to the profile with a notice" true (List.mem "/profile?changed=username" (Dream.headers changed "Location")); (* The account now answers to the new name, and no longer to the old one. *) let renamed = Lwt_main.run (Test_repo.find_trainee repo trainee.Trainee.id) in Alcotest.(check (option string)) "stored under the new username" (Some "athlete") (Option.map (fun t -> Trainee.username_to_string t.Trainee.username) renamed); Alcotest.(check bool) "old name no longer resolves" true (Option.is_none (Lwt_main.run (match Trainee.username "lifter" with | Ok u -> Test_repo.find_trainee_by_username repo u | Error _ -> Lwt.return None))) ); ( "a taken username is refused and the account keeps its name", `Quick, fun () -> let c, repo = client_with_repo () in let _ = register c ~username:"lifter" ~password:"heavyduty1" in let trainee = the_trainee repo ~username:"lifter" in (* Register a second account in the same store. *) let other = { c with jar = [] } in let _ = register other ~username:"taken" ~password:"heavyduty1" in let token = Option.get (csrf_token (body (get c "/profile"))) in let refused = post c "/profile/username" [ ("dream.csrf", token); ("username", "taken") ] in Alcotest.(check int) "renders an invalid response" 400 (status refused); (* The account still holds its original name. *) Alcotest.(check (option string)) "name unchanged after refusal" (Some "lifter") (Option.map (fun t -> Trainee.username_to_string t.Trainee.username) (Lwt_main.run (Test_repo.find_trainee repo trainee.Trainee.id))) ); ( "changing the password requires the current one", `Quick, fun () -> let c = client () in let _ = sign_in_new c in let token = Option.get (csrf_token (body (get c "/profile"))) in let refused = post c "/profile/password" [ ("dream.csrf", token); ("current", "wrongpass"); ("next", "newsecret1"); ] in Alcotest.(check int) "renders an invalid response" 400 (status refused); (* Now change it with the correct current password. *) let token = Option.get (csrf_token (body (get c "/profile"))) in let changed = post c "/profile/password" [ ("dream.csrf", token); ("current", "heavyduty1"); ("next", "newsecret1"); ] in Alcotest.(check int) "redirects after the change" 303 (status changed); Alcotest.(check bool) "redirects back to the profile with a notice" true (List.mem "/profile?changed=password" (Dream.headers changed "Location")); (* The new password now authenticates and the old one no longer does. Sign out, then prove the change through the sign-in flow. *) let token = Option.get (csrf_token (body (get c "/profile"))) in let _ = post c "/logout" [ ("dream.csrf", token) ] in let old_pw = sign_in c ~username:"lifter" ~password:"heavyduty1" in Alcotest.(check int) "the old password no longer works" 401 (status old_pw); let new_pw = sign_in c ~username:"lifter" ~password:"newsecret1" in Alcotest.(check int) "the new password authenticates" 303 (status new_pw) ); ( "the settings page is separate from the profile page", `Quick, fun () -> let c = client () in let _ = sign_in_new c in Alcotest.(check int) "settings served" 200 (status (get c "/settings")); Alcotest.(check int) "profile served separately" 200 (status (get c "/profile")) ); ( "the initial release hides the import", `Quick, fun () -> let c = client ~import_open:false () in let _ = sign_in_new c in let profile = body (get c "/profile") in Alcotest.(check bool) "profile offers no upload" true (Option.is_none (index_from ~needle:"Upload import" profile 0)); Alcotest.(check int) "import route is not served" 404 (status (post c "/profile/import" [])) ); ( "the settings theme form redirects after a valid update", `Quick, fun () -> let c = client () in let _ = sign_in_new c in let token = Option.get (csrf_token (body (get c "/settings"))) in let updated = post c "/settings/theme" [ ("dream.csrf", token); ("theme", "dark") ] in Alcotest.(check int) "theme update redirects" 303 (status updated); Alcotest.(check string) "returns to settings" "/settings" (List.hd (Dream.headers updated "Location")) ); ] ); ]