OCaml library for Crockford's Base32
1(* Test suite using Alcotest - ported from crockford_test.go *)
2
3(* Encode tests *)
4let test_encode () =
5 let test_cases = [
6 (0L, 0, 0, false, "0");
7 (1234L, 0, 0, false, "16j");
8 (1234L, 2, 0, false, "16-j");
9 (1234L, 2, 4, false, "01-6j");
10 (538751765283013L, 5, 10, false, "f9zqn-sf065");
11 (736381604818L, 5, 10, true, "ndsw7-4yj20");
12 (258706475165200172L, 7, 14, true, "75rw5cg-n1bsc64");
13 (161006169L, 4, 8, true, "4shg-js75");
14 ] in
15 List.iter (fun (input, split_every, length, checksum, expected) ->
16 let result = Crockford.encode ~split_every ~min_length:length ~checksum input in
17 let test_name = Printf.sprintf "encode %Ld (split=%d, len=%d, checksum=%b)" input split_every length checksum in
18 Alcotest.(check string) test_name expected result
19 ) test_cases
20
21(* Generate tests *)
22let test_generate () =
23 Random.self_init ();
24
25 let test_cases = [
26 (4, 0, false);
27 (10, 5, false);
28 (10, 5, true);
29 ] in
30 List.iter (fun (length, split_every, checksum) ->
31 let result = Crockford.generate ~length ~split_every ~checksum () in
32 let expected_length =
33 if split_every > 0 then
34 length + (length / split_every) - 1
35 else
36 length
37 in
38 let test_name = Printf.sprintf "generate length=%d split=%d checksum=%b" length split_every checksum in
39 Alcotest.(check int) test_name expected_length (String.length result)
40 ) test_cases
41
42(* Decode tests *)
43let test_decode () =
44 let test_cases = [
45 ("0", false, Some 0L);
46 ("16j", false, Some 1234L);
47 ("16-j", false, Some 1234L);
48 ("01-6j", false, Some 1234L);
49 ("f9zqn-sf065", false, Some 538751765283013L);
50 ("twwjw-1ww98", true, Some 924377286556L);
51 ("9ed5m-ytn", false, Some 324712168277L);
52 ("9ed5m-ytn30", true, Some 324712168277L);
53 ("elife.01567", false, None); (* Should fail - contains invalid character '.' *)
54 ] in
55 List.iter (fun (input, checksum, expected) ->
56 let test_name = Printf.sprintf "decode '%s' (checksum=%b)" input checksum in
57 match expected with
58 | Some want ->
59 let got = Crockford.decode ~checksum input in
60 Alcotest.(check int64) test_name want got
61 | None ->
62 (* Expected to fail *)
63 Alcotest.check_raises
64 test_name
65 (Crockford.Decode_error (Crockford.Invalid_character {
66 char = '.';
67 message = "character '.' not in base32 alphabet"
68 }))
69 (fun () -> ignore (Crockford.decode ~checksum input))
70 ) test_cases
71
72(* Normalize tests *)
73let test_normalize () =
74 let test_cases = [
75 ("f9ZQNSF065", "f9zqnsf065");
76 ("f9zqn-sf065", "f9zqnsf065");
77 ("f9Llio", "f91110");
78 ] in
79 List.iter (fun (input, expected) ->
80 let result = Crockford.normalize input in
81 let test_name = Printf.sprintf "normalize '%s'" input in
82 Alcotest.(check string) test_name expected result
83 ) test_cases
84
85(* GenerateChecksum tests *)
86let test_generate_checksum () =
87 let test_cases = [
88 (450320459383L, 85L);
89 (123456789012L, 44L);
90 ] in
91 List.iter (fun (input, expected) ->
92 let result = Crockford.generate_checksum input in
93 let test_name = Printf.sprintf "generate_checksum %Ld" input in
94 Alcotest.(check int64) test_name expected result
95 ) test_cases
96
97(* Validate tests *)
98let test_validate () =
99 let test_cases = [
100 (375301249367L, 92L, true);
101 (930412369850L, 36L, true);
102 ] in
103 List.iter (fun (input, checksum, expected) ->
104 let result = Crockford.validate input ~checksum in
105 let test_name = Printf.sprintf "validate %Ld checksum=%Ld" input checksum in
106 Alcotest.(check bool) test_name expected result
107 ) test_cases
108
109(* Additional roundtrip tests *)
110let test_roundtrip () =
111 let test_numbers = [
112 0L;
113 1L;
114 32L;
115 1024L;
116 1234567890L;
117 Int64.max_int;
118 ] in
119
120 List.iter (fun num ->
121 let encoded = Crockford.encode num in
122 let decoded = Crockford.decode encoded in
123 Alcotest.(check int64) (Printf.sprintf "roundtrip %Ld" num) num decoded
124 ) test_numbers
125
126let test_roundtrip_with_checksum () =
127 let test_numbers = [
128 0L;
129 1L;
130 32L;
131 1024L;
132 1234567890L;
133 ] in
134
135 List.iter (fun num ->
136 let encoded = Crockford.encode ~checksum:true num in
137 let decoded = Crockford.decode ~checksum:true encoded in
138 Alcotest.(check int64) (Printf.sprintf "roundtrip with checksum %Ld" num) num decoded
139 ) test_numbers
140
141(* Error handling tests *)
142let test_error_invalid_character () =
143 Alcotest.check_raises
144 "invalid character"
145 (Crockford.Decode_error (Crockford.Invalid_character {
146 char = '#';
147 message = "character '#' not in base32 alphabet"
148 }))
149 (fun () -> ignore (Crockford.decode "abc#def"))
150
151let test_error_invalid_checksum () =
152 Alcotest.check_raises
153 "invalid checksum length"
154 (Crockford.Decode_error (Crockford.Invalid_checksum {
155 checksum = "ab";
156 message = "encoded string too short for checksum"
157 }))
158 (fun () -> ignore (Crockford.decode ~checksum:true "ab"))
159
160let test_error_checksum_mismatch () =
161 let encoded = Crockford.encode ~checksum:true 1234L in
162 let corrupted = String.sub encoded 0 (String.length encoded - 2) ^ "00" in
163
164 Alcotest.check_raises
165 "checksum mismatch"
166 (Crockford.Decode_error (Crockford.Checksum_mismatch {
167 expected = Crockford.generate_checksum 1234L;
168 got = 0L;
169 identifier = corrupted
170 }))
171 (fun () -> ignore (Crockford.decode ~checksum:true corrupted))
172
173let test_error_invalid_length () =
174 Alcotest.check_raises
175 "invalid length for generate"
176 (Crockford.Decode_error (Crockford.Invalid_length {
177 length = 2;
178 message = "length must be >= 3 if checksum is enabled"
179 }))
180 (fun () -> ignore (Crockford.generate ~length:2 ~checksum:true ()))
181
182let () =
183 let open Alcotest in
184 run "Crockford" [
185 "encoding", [
186 test_case "encode" `Quick test_encode;
187 ];
188 "decoding", [
189 test_case "decode" `Quick test_decode;
190 ];
191 "normalization", [
192 test_case "normalize" `Quick test_normalize;
193 ];
194 "checksum", [
195 test_case "generate_checksum" `Quick test_generate_checksum;
196 test_case "validate" `Quick test_validate;
197 ];
198 "generation", [
199 test_case "generate" `Quick test_generate;
200 ];
201 "roundtrip", [
202 test_case "roundtrip encoding/decoding" `Quick test_roundtrip;
203 test_case "roundtrip with checksum" `Quick test_roundtrip_with_checksum;
204 ];
205 "errors", [
206 test_case "invalid character" `Quick test_error_invalid_character;
207 test_case "invalid checksum" `Quick test_error_invalid_checksum;
208 test_case "checksum mismatch" `Quick test_error_checksum_mismatch;
209 test_case "invalid length" `Quick test_error_invalid_length;
210 ];
211 ]