a database layer insipred by caqti and ecto
at main 11 kB view raw
1open Repodb 2open Common 3 4let test_create_changeset () = 5 let user = make_user () in 6 let cs = Changeset.create user in 7 Alcotest.(check bool) "new changeset is valid" true (Changeset.is_valid cs) 8 9let test_cast_filters_fields () = 10 let user = make_user () in 11 let cs = 12 Changeset.create user 13 |> Changeset.cast 14 [ ("name", "Alice"); ("unknown", "value") ] 15 ~fields:[ name_field ] 16 in 17 let changes = Changeset.changes cs in 18 Alcotest.(check int) "only allowed fields" 1 (List.length changes); 19 Alcotest.(check bool) "has name change" true (List.mem_assoc "name" changes) 20 21let test_validate_required_passes () = 22 let user = make_user () in 23 let cs = 24 Changeset.create user 25 |> Changeset.cast [ ("name", "Alice") ] ~fields:[ name_field ] 26 |> Changeset.validate_required [ name_field ] 27 in 28 Alcotest.(check bool) "valid with required field" true (Changeset.is_valid cs) 29 30let test_validate_required_fails_empty () = 31 let user = make_user () in 32 let cs = 33 Changeset.create user 34 |> Changeset.cast [ ("name", "") ] ~fields:[ name_field ] 35 |> Changeset.validate_required [ name_field ] 36 in 37 Alcotest.(check bool) 38 "invalid with empty required" false (Changeset.is_valid cs) 39 40let test_validate_required_fails_missing () = 41 let user = make_user () in 42 let cs = 43 Changeset.create user 44 |> Changeset.cast [] ~fields:[ name_field ] 45 |> Changeset.validate_required [ name_field ] 46 in 47 Alcotest.(check bool) 48 "invalid with missing required" false (Changeset.is_valid cs) 49 50let test_validate_format_passes () = 51 let user = make_user () in 52 let cs = 53 Changeset.create user 54 |> Changeset.cast [ ("email", "test@example.com") ] ~fields:[ email_field ] 55 |> Changeset.validate_format email_field ~pattern:"^[^@]+@[^@]+$" 56 in 57 Alcotest.(check bool) "valid email format" true (Changeset.is_valid cs) 58 59let test_validate_format_fails () = 60 let user = make_user () in 61 let cs = 62 Changeset.create user 63 |> Changeset.cast [ ("email", "invalid-email") ] ~fields:[ email_field ] 64 |> Changeset.validate_format email_field ~pattern:"^[^@]+@[^@]+$" 65 in 66 Alcotest.(check bool) "invalid email format" false (Changeset.is_valid cs) 67 68let test_validate_length_min () = 69 let user = make_user () in 70 let cs = 71 Changeset.create user 72 |> Changeset.cast [ ("name", "Al") ] ~fields:[ name_field ] 73 |> Changeset.validate_length name_field ~min:3 74 in 75 Alcotest.(check bool) "too short" false (Changeset.is_valid cs) 76 77let test_validate_length_max () = 78 let user = make_user () in 79 let cs = 80 Changeset.create user 81 |> Changeset.cast [ ("name", "A very long name") ] ~fields:[ name_field ] 82 |> Changeset.validate_length name_field ~max:5 83 in 84 Alcotest.(check bool) "too long" false (Changeset.is_valid cs) 85 86let test_validate_length_exact () = 87 let user = make_user () in 88 let cs = 89 Changeset.create user 90 |> Changeset.cast [ ("name", "Alice") ] ~fields:[ name_field ] 91 |> Changeset.validate_length name_field ~is:5 92 in 93 Alcotest.(check bool) "exact length valid" true (Changeset.is_valid cs) 94 95let test_validate_inclusion () = 96 let user = make_user () in 97 let cs = 98 Changeset.create user 99 |> Changeset.cast [ ("name", "admin") ] ~fields:[ name_field ] 100 |> Changeset.validate_inclusion name_field 101 ~values:[ "admin"; "user"; "guest" ] 102 in 103 Alcotest.(check bool) "value in list" true (Changeset.is_valid cs) 104 105let test_validate_inclusion_fails () = 106 let user = make_user () in 107 let cs = 108 Changeset.create user 109 |> Changeset.cast [ ("name", "hacker") ] ~fields:[ name_field ] 110 |> Changeset.validate_inclusion name_field 111 ~values:[ "admin"; "user"; "guest" ] 112 in 113 Alcotest.(check bool) "value not in list" false (Changeset.is_valid cs) 114 115let test_validate_exclusion () = 116 let user = make_user () in 117 let cs = 118 Changeset.create user 119 |> Changeset.cast [ ("name", "admin") ] ~fields:[ name_field ] 120 |> Changeset.validate_exclusion name_field ~values:[ "root"; "system" ] 121 in 122 Alcotest.(check bool) "value not excluded" true (Changeset.is_valid cs) 123 124let test_validate_exclusion_fails () = 125 let user = make_user () in 126 let cs = 127 Changeset.create user 128 |> Changeset.cast [ ("name", "root") ] ~fields:[ name_field ] 129 |> Changeset.validate_exclusion name_field ~values:[ "root"; "system" ] 130 in 131 Alcotest.(check bool) "value excluded" false (Changeset.is_valid cs) 132 133let test_validate_number_greater_than () = 134 let user = make_user () in 135 let cs = 136 Changeset.create user 137 |> Changeset.cast [ ("age", "25") ] ~fields:[ age_field ] 138 |> Changeset.validate_number age_field ~greater_than:18 139 in 140 Alcotest.(check bool) "number greater than" true (Changeset.is_valid cs) 141 142let test_validate_number_greater_than_fails () = 143 let user = make_user () in 144 let cs = 145 Changeset.create user 146 |> Changeset.cast [ ("age", "15") ] ~fields:[ age_field ] 147 |> Changeset.validate_number age_field ~greater_than:18 148 in 149 Alcotest.(check bool) "number not greater than" false (Changeset.is_valid cs) 150 151let test_add_error () = 152 let user = make_user () in 153 let cs = 154 Changeset.create user 155 |> Changeset.add_error ~field:"email" ~message:"is taken" 156 ~validation:"unique" 157 in 158 Alcotest.(check bool) "has error" false (Changeset.is_valid cs); 159 Alcotest.(check int) "error count" 1 (List.length (Changeset.errors cs)) 160 161let test_error_messages () = 162 let user = make_user () in 163 let cs = 164 Changeset.create user 165 |> Changeset.add_error ~field:"name" ~message:"can't be blank" 166 ~validation:"required" 167 |> Changeset.add_error ~field:"email" ~message:"is invalid" 168 ~validation:"format" 169 in 170 let msgs = Changeset.error_messages cs in 171 Alcotest.(check int) "two error messages" 2 (List.length msgs) 172 173let test_get_change () = 174 let user = make_user () in 175 let cs = 176 Changeset.create user 177 |> Changeset.cast [ ("name", "Alice") ] ~fields:[ name_field ] 178 in 179 Alcotest.(check (option string)) 180 "get change" (Some "Alice") 181 (Changeset.get_change cs name_field) 182 183let test_put_change () = 184 let user = make_user () in 185 let cs = Changeset.create user |> Changeset.put_change name_field "Bob" in 186 Alcotest.(check (option string)) 187 "put change" (Some "Bob") 188 (Changeset.get_change cs name_field) 189 190let test_delete_change () = 191 let user = make_user () in 192 let cs = 193 Changeset.create user 194 |> Changeset.cast [ ("name", "Alice") ] ~fields:[ name_field ] 195 |> Changeset.delete_change name_field 196 in 197 Alcotest.(check (option string)) 198 "delete change" None 199 (Changeset.get_change cs name_field) 200 201let test_for_insert () = 202 let user = make_user () in 203 let cs = Changeset.for_insert user in 204 Alcotest.(check (option bool)) 205 "action is insert" (Some true) 206 (Option.map (fun a -> a = Changeset.Insert) (Changeset.action cs)) 207 208let test_for_update () = 209 let user = make_user () in 210 let cs = Changeset.for_update user in 211 Alcotest.(check (option bool)) 212 "action is update" (Some true) 213 (Option.map (fun a -> a = Changeset.Update) (Changeset.action cs)) 214 215let test_apply_action_valid () = 216 let user = make_user ~name:"Alice" () in 217 let cs = Changeset.create user in 218 match Changeset.apply_action cs with 219 | Ok data -> Alcotest.(check string) "returns data" "Alice" data.name 220 | Error _ -> Alcotest.fail "should not error" 221 222let test_apply_action_invalid () = 223 let user = make_user () in 224 let cs = 225 Changeset.create user 226 |> Changeset.add_error ~field:"name" ~message:"error" ~validation:"test" 227 in 228 match Changeset.apply_action cs with 229 | Ok _ -> Alcotest.fail "should error" 230 | Error errs -> Alcotest.(check int) "returns errors" 1 (List.length errs) 231 232let test_unique_constraint () = 233 let user = make_user () in 234 let cs = Changeset.create user |> Changeset.unique_constraint email_field in 235 Alcotest.(check bool) "still valid" true (Changeset.is_valid cs) 236 237let test_validate_confirmation () = 238 let password_field : (user, string) Field.t = 239 Field.make ~table_name:"users" ~name:"password" ~ty:Types.string 240 ~get:(fun _ -> "") 241 ~set:(fun _ u -> u) 242 () 243 in 244 let password_confirmation_field : (user, string) Field.t = 245 Field.make ~table_name:"users" ~name:"password_confirmation" 246 ~ty:Types.string 247 ~get:(fun _ -> "") 248 ~set:(fun _ u -> u) 249 () 250 in 251 let user = make_user () in 252 let cs = 253 Changeset.create user 254 |> Changeset.cast 255 [ ("password", "secret123"); ("password_confirmation", "secret123") ] 256 ~fields:[ password_field; password_confirmation_field ] 257 |> Changeset.validate_confirmation password_field 258 ~confirmation_field:password_confirmation_field 259 in 260 Alcotest.(check bool) "passwords match" true (Changeset.is_valid cs) 261 262let test_validate_confirmation_fails () = 263 let password_field : (user, string) Field.t = 264 Field.make ~table_name:"users" ~name:"password" ~ty:Types.string 265 ~get:(fun _ -> "") 266 ~set:(fun _ u -> u) 267 () 268 in 269 let password_confirmation_field : (user, string) Field.t = 270 Field.make ~table_name:"users" ~name:"password_confirmation" 271 ~ty:Types.string 272 ~get:(fun _ -> "") 273 ~set:(fun _ u -> u) 274 () 275 in 276 let user = make_user () in 277 let cs = 278 Changeset.create user 279 |> Changeset.cast 280 [ ("password", "secret123"); ("password_confirmation", "different") ] 281 ~fields:[ password_field; password_confirmation_field ] 282 |> Changeset.validate_confirmation password_field 283 ~confirmation_field:password_confirmation_field 284 in 285 Alcotest.(check bool) "passwords don't match" false (Changeset.is_valid cs) 286 287let tests = 288 [ 289 ("create changeset", `Quick, test_create_changeset); 290 ("cast filters fields", `Quick, test_cast_filters_fields); 291 ("validate_required passes", `Quick, test_validate_required_passes); 292 ("validate_required fails empty", `Quick, test_validate_required_fails_empty); 293 ( "validate_required fails missing", 294 `Quick, 295 test_validate_required_fails_missing ); 296 ("validate_format passes", `Quick, test_validate_format_passes); 297 ("validate_format fails", `Quick, test_validate_format_fails); 298 ("validate_length min", `Quick, test_validate_length_min); 299 ("validate_length max", `Quick, test_validate_length_max); 300 ("validate_length exact", `Quick, test_validate_length_exact); 301 ("validate_inclusion", `Quick, test_validate_inclusion); 302 ("validate_inclusion fails", `Quick, test_validate_inclusion_fails); 303 ("validate_exclusion", `Quick, test_validate_exclusion); 304 ("validate_exclusion fails", `Quick, test_validate_exclusion_fails); 305 ("validate_number greater_than", `Quick, test_validate_number_greater_than); 306 ( "validate_number greater_than fails", 307 `Quick, 308 test_validate_number_greater_than_fails ); 309 ("add_error", `Quick, test_add_error); 310 ("error_messages", `Quick, test_error_messages); 311 ("get_change", `Quick, test_get_change); 312 ("put_change", `Quick, test_put_change); 313 ("delete_change", `Quick, test_delete_change); 314 ("for_insert", `Quick, test_for_insert); 315 ("for_update", `Quick, test_for_update); 316 ("apply_action valid", `Quick, test_apply_action_valid); 317 ("apply_action invalid", `Quick, test_apply_action_invalid); 318 ("unique_constraint", `Quick, test_unique_constraint); 319 ("validate_confirmation", `Quick, test_validate_confirmation); 320 ("validate_confirmation fails", `Quick, test_validate_confirmation_fails); 321 ]