View raw

1 open Hito_app 2 open Support 3 4 let suite = 5 [ 6 ( "web.app_feedback", 7 [ 8 ( "the app feedback screen and its tabs are served", 9 `Quick, 10 fun () -> 11 let c = client () in 12 let _ = sign_in_new c in 13 Alcotest.(check int) 14 "write tab served" 200 15 (status (get c "/app-feedback")); 16 Alcotest.(check int) 17 "submitted tab served" 200 18 (status (get c "/app-feedback?tab=submitted")) ); 19 ( "blank app feedback is refused; a real message is stored", 20 `Quick, 21 fun () -> 22 let c, repo = client_with_repo () in 23 let _ = register c ~username:"lifter" ~password:"heavyduty1" in 24 let trainee = the_trainee repo ~username:"lifter" in 25 let stored_count () = 26 List.length 27 (Lwt_main.run 28 (Test_repo.app_feedback repo ~viewer:trainee.Trainee.id)) 29 in 30 let token = 31 Option.get (csrf_token (body (get c "/app-feedback"))) 32 in 33 let blank = 34 post c "/app-feedback/submit" 35 [ ("dream.csrf", token); ("message", " ") ] 36 in 37 Alcotest.(check int) "blank message redirects" 303 (status blank); 38 Alcotest.(check bool) 39 "redirects to the write tab" true 40 (List.mem "/app-feedback?tab=write" 41 (Dream.headers blank "Location")); 42 (* Blank input stored nothing. *) 43 Alcotest.(check int) 44 "no report stored for blank input" 0 (stored_count ()); 45 let token = 46 Option.get (csrf_token (body (get c "/app-feedback"))) 47 in 48 let submitted = 49 post c "/app-feedback/submit" 50 [ 51 ("dream.csrf", token); 52 ("message", "The app is clear.\nThank you!"); 53 ] 54 in 55 Alcotest.(check int) "message is accepted" 303 (status submitted); 56 Alcotest.(check bool) 57 "redirects to submitted feedback" true 58 (List.mem "/app-feedback?tab=submitted" 59 (Dream.headers submitted "Location")); 60 (* The report now exists with the exact message. *) 61 let reports = 62 Lwt_main.run 63 (Test_repo.app_feedback repo ~viewer:trainee.Trainee.id) 64 in 65 Alcotest.(check int) "one report stored" 1 (List.length reports); 66 Alcotest.(check string) 67 "stores the exact message" "The app is clear.\nThank you!" 68 (List.hd reports).Repository.message ); 69 ( "feedback records its author and accepts another trainee's vote", 70 `Quick, 71 fun () -> 72 let alice = client () in 73 let _ = register alice ~username:"alice" ~password:"heavyduty1" in 74 let bob = { alice with jar = [] } in 75 let _ = register bob ~username:"bobby" ~password:"heavyduty1" in 76 let submit client message = 77 let token = 78 Option.get (csrf_token (body (get client "/app-feedback"))) 79 in 80 post client "/app-feedback/submit" 81 [ ("dream.csrf", token); ("message", message) ] 82 in 83 Alcotest.(check int) 84 "alice feedback accepted" 303 85 (status (submit alice "Alice report")); 86 Alcotest.(check int) 87 "bob feedback accepted" 303 88 (status (submit bob "Bob report")); 89 (* Bob's report is t2:1 — the vote control on Alice's view posts 90 there. Alice upvotes it. *) 91 let list_page = body (get alice "/app-feedback?tab=submitted") in 92 let token = Option.get (csrf_token list_page) in 93 let voted = 94 post alice "/app-feedback/t2:1/upvote" [ ("dream.csrf", token) ] 95 in 96 Alcotest.(check int) "upvote redirects" 303 (status voted) ); 97 ( "an owner can edit and remove their feedback", 98 `Quick, 99 fun () -> 100 let c, repo = client_with_repo () in 101 let _ = register c ~username:"lifter" ~password:"heavyduty1" in 102 let trainee = the_trainee repo ~username:"lifter" in 103 let token = 104 Option.get (csrf_token (body (get c "/app-feedback"))) 105 in 106 let _ = 107 post c "/app-feedback/submit" 108 [ ("dream.csrf", token); ("message", "First report") ] 109 in 110 let feedback_id () = 111 (List.hd 112 (Lwt_main.run 113 (Test_repo.app_feedback repo ~viewer:trainee.Trainee.id))) 114 .Repository.feedback_id |> Repository.app_feedback_id_to_string 115 in 116 let id = feedback_id () in 117 (* A blank edit is refused and changes nothing. *) 118 let token = 119 Option.get 120 (csrf_token (body (get c "/app-feedback?tab=submitted"))) 121 in 122 let blank = 123 post c 124 ("/app-feedback/" ^ id ^ "/edit") 125 [ ("dream.csrf", token); ("message", " ") ] 126 in 127 Alcotest.(check int) "blank edit redirects" 303 (status blank); 128 Alcotest.(check string) 129 "message unchanged by blank edit" "First report" 130 (List.hd 131 (Lwt_main.run 132 (Test_repo.app_feedback repo ~viewer:trainee.Trainee.id))) 133 .Repository.message; 134 (* A real edit changes the stored message. *) 135 let token = 136 Option.get 137 (csrf_token (body (get c "/app-feedback?tab=submitted"))) 138 in 139 let edited = 140 post c 141 ("/app-feedback/" ^ id ^ "/edit") 142 [ ("dream.csrf", token); ("message", "Edited report") ] 143 in 144 Alcotest.(check int) "edit redirects" 303 (status edited); 145 Alcotest.(check string) 146 "message updated by edit" "Edited report" 147 (List.hd 148 (Lwt_main.run 149 (Test_repo.app_feedback repo ~viewer:trainee.Trainee.id))) 150 .Repository.message; 151 (* Removal deletes the report. *) 152 let token = 153 Option.get 154 (csrf_token (body (get c "/app-feedback?tab=submitted"))) 155 in 156 let removed = 157 post c 158 ("/app-feedback/" ^ id ^ "/remove") 159 [ ("dream.csrf", token) ] 160 in 161 Alcotest.(check int) "remove redirects" 303 (status removed); 162 Alcotest.(check int) 163 "report removed" 0 164 (List.length 165 (Lwt_main.run 166 (Test_repo.app_feedback repo ~viewer:trainee.Trainee.id))) 167 ); 168 ( "another trainee cannot edit or remove app feedback", 169 `Quick, 170 fun () -> 171 let alice, repo = client_with_repo () in 172 let _ = register alice ~username:"alice" ~password:"heavyduty1" in 173 let bob = { alice with jar = [] } in 174 let _ = register bob ~username:"bobby" ~password:"heavyduty1" in 175 let bob_trainee = the_trainee repo ~username:"bobby" in 176 (* Bob owns t2:1. Alice attempts to edit and remove it. Both are 177 refused at the ownership check, so the report and its message 178 survive. *) 179 let token = 180 Option.get (csrf_token (body (get bob "/app-feedback"))) 181 in 182 let _ = 183 post bob "/app-feedback/submit" 184 [ ("dream.csrf", token); ("message", "Bob owns this") ] 185 in 186 let stored () = 187 Lwt_main.run 188 (Test_repo.app_feedback repo ~viewer:bob_trainee.Trainee.id) 189 in 190 Alcotest.(check int) 191 "one report before cross-user attempts" 1 192 (List.length (stored ())); 193 let token = 194 Option.get 195 (csrf_token (body (get alice "/app-feedback?tab=submitted"))) 196 in 197 let edited = 198 post alice "/app-feedback/t2:1/edit" 199 [ ("dream.csrf", token); ("message", "Taken over") ] 200 in 201 Alcotest.(check int) "cross-user edit redirects" 303 (status edited); 202 let token = 203 Option.get 204 (csrf_token (body (get alice "/app-feedback?tab=submitted"))) 205 in 206 let removed = 207 post alice "/app-feedback/t2:1/remove" [ ("dream.csrf", token) ] 208 in 209 Alcotest.(check int) 210 "cross-user remove redirects" 303 (status removed); 211 (* The report still exists with its original message: the cross-user 212 attempts changed nothing. *) 213 let after = stored () in 214 Alcotest.(check int) 215 "report survives cross-user attempts" 1 (List.length after); 216 Alcotest.(check string) 217 "message is unchanged" "Bob owns this" 218 (List.hd after).Repository.message ); 219 ] ); 220 ] 221