a database layer insipred by caqti and ecto
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 ]