Line data Source code
1 : #include "test_helpers.h"
2 : #include "json_util.h"
3 : #include <stdlib.h>
4 : #include <string.h>
5 :
6 : /* ── json_get_string ────────────────────────────────────────────────── */
7 :
8 1 : static void test_json_get_string_simple(void) {
9 1 : const char *j = "{\"id\": \"abc123\"}";
10 1 : char *v = json_get_string(j, "id");
11 1 : ASSERT(v != NULL, "json_get_string: found");
12 1 : ASSERT(strcmp(v, "abc123") == 0, "json_get_string: value matches");
13 1 : free(v);
14 : }
15 :
16 1 : static void test_json_get_string_second_key(void) {
17 1 : const char *j = "{\"id\": \"abc\", \"name\": \"hello\"}";
18 1 : char *v = json_get_string(j, "name");
19 1 : ASSERT(v != NULL, "json_get_string second key: found");
20 1 : ASSERT(strcmp(v, "hello") == 0, "json_get_string second key: value");
21 1 : free(v);
22 : }
23 :
24 1 : static void test_json_get_string_not_found(void) {
25 1 : const char *j = "{\"id\": \"abc\"}";
26 1 : char *v = json_get_string(j, "missing");
27 1 : ASSERT(v == NULL, "json_get_string not found: returns NULL");
28 : }
29 :
30 1 : static void test_json_get_string_escape(void) {
31 1 : const char *j = "{\"msg\": \"hello\\nworld\\t!\"}";
32 1 : char *v = json_get_string(j, "msg");
33 1 : ASSERT(v != NULL, "json_get_string escape: found");
34 1 : ASSERT(strcmp(v, "hello\nworld\t!") == 0, "json_get_string escape: unescaped");
35 1 : free(v);
36 : }
37 :
38 1 : static void test_json_get_string_escaped_quote(void) {
39 1 : const char *j = "{\"msg\": \"say \\\"hi\\\"\"}";
40 1 : char *v = json_get_string(j, "msg");
41 1 : ASSERT(v != NULL, "json_get_string escaped quote: found");
42 1 : ASSERT(strcmp(v, "say \"hi\"") == 0, "json_get_string escaped quote: value");
43 1 : free(v);
44 : }
45 :
46 1 : static void test_json_get_string_skip_nested_object(void) {
47 1 : const char *j = "{\"a\": {\"x\": 1}, \"b\": \"found\"}";
48 1 : char *v = json_get_string(j, "b");
49 1 : ASSERT(v != NULL, "json skip nested obj: found");
50 1 : ASSERT(strcmp(v, "found") == 0, "json skip nested obj: value");
51 1 : free(v);
52 : }
53 :
54 1 : static void test_json_get_string_skip_nested_array(void) {
55 1 : const char *j = "{\"arr\": [1, 2, 3], \"key\": \"val\"}";
56 1 : char *v = json_get_string(j, "key");
57 1 : ASSERT(v != NULL, "json skip nested array: found");
58 1 : ASSERT(strcmp(v, "val") == 0, "json skip nested array: value");
59 1 : free(v);
60 : }
61 :
62 1 : static void test_json_get_string_null_input(void) {
63 1 : ASSERT(json_get_string(NULL, "k") == NULL, "json NULL json");
64 1 : ASSERT(json_get_string("{}", NULL) == NULL, "json NULL key");
65 : }
66 :
67 1 : static void test_json_get_string_empty_object(void) {
68 1 : ASSERT(json_get_string("{}", "k") == NULL, "json empty object");
69 : }
70 :
71 1 : static void test_json_get_string_whitespace(void) {
72 1 : const char *j = " { \"id\" : \"val\" } ";
73 1 : char *v = json_get_string(j, "id");
74 1 : ASSERT(v != NULL, "json whitespace: found");
75 1 : ASSERT(strcmp(v, "val") == 0, "json whitespace: value");
76 1 : free(v);
77 : }
78 :
79 1 : static void test_json_get_string_empty_value(void) {
80 1 : const char *j = "{\"e\": \"\"}";
81 1 : char *v = json_get_string(j, "e");
82 1 : ASSERT(v != NULL, "json empty value: found");
83 1 : ASSERT(v[0] == '\0', "json empty value: empty string");
84 1 : free(v);
85 : }
86 :
87 1 : static void test_json_get_string_unicode_escape(void) {
88 1 : const char *j = "{\"u\": \"\\u0041\"}";
89 1 : char *v = json_get_string(j, "u");
90 1 : ASSERT(v != NULL, "json unicode escape: found");
91 1 : ASSERT(v[0] == 'A' && v[1] == '\0', "json unicode escape: A");
92 1 : free(v);
93 : }
94 :
95 1 : static void test_json_get_string_backslash(void) {
96 1 : const char *j = "{\"p\": \"C:\\\\Users\"}";
97 1 : char *v = json_get_string(j, "p");
98 1 : ASSERT(v != NULL, "json backslash: found");
99 1 : ASSERT(strcmp(v, "C:\\Users") == 0, "json backslash: value");
100 1 : free(v);
101 : }
102 :
103 : /* ── json_get_int ───────────────────────────────────────────────────── */
104 :
105 1 : static void test_json_get_int_simple(void) {
106 1 : const char *j = "{\"count\": 42}";
107 1 : int v = 0;
108 1 : ASSERT(json_get_int(j, "count", &v) == 0, "json_get_int: found");
109 1 : ASSERT(v == 42, "json_get_int: value");
110 : }
111 :
112 1 : static void test_json_get_int_negative(void) {
113 1 : const char *j = "{\"val\": -7}";
114 1 : int v = 0;
115 1 : ASSERT(json_get_int(j, "val", &v) == 0, "json_get_int negative: found");
116 1 : ASSERT(v == -7, "json_get_int negative: value");
117 : }
118 :
119 1 : static void test_json_get_int_quoted(void) {
120 1 : const char *j = "{\"n\": \"123\"}";
121 1 : int v = 0;
122 1 : ASSERT(json_get_int(j, "n", &v) == 0, "json_get_int quoted: found");
123 1 : ASSERT(v == 123, "json_get_int quoted: value");
124 : }
125 :
126 1 : static void test_json_get_int_not_found(void) {
127 1 : const char *j = "{\"a\": 1}";
128 1 : int v = 0;
129 1 : ASSERT(json_get_int(j, "b", &v) == -1, "json_get_int not found");
130 : }
131 :
132 1 : static void test_json_get_int_null_input(void) {
133 1 : int v = 0;
134 1 : ASSERT(json_get_int(NULL, "k", &v) == -1, "json_get_int NULL");
135 : }
136 :
137 : /* ── json_get_string_array ──────────────────────────────────────────── */
138 :
139 1 : static void test_json_string_array_simple(void) {
140 1 : const char *j = "{\"labels\": [\"INBOX\", \"Work\", \"STARRED\"]}";
141 1 : char **arr = NULL; int count = 0;
142 1 : ASSERT(json_get_string_array(j, "labels", &arr, &count) == 0, "str_array: ok");
143 1 : ASSERT(count == 3, "str_array: count=3");
144 1 : ASSERT(strcmp(arr[0], "INBOX") == 0, "str_array[0]=INBOX");
145 1 : ASSERT(strcmp(arr[1], "Work") == 0, "str_array[1]=Work");
146 1 : ASSERT(strcmp(arr[2], "STARRED") == 0, "str_array[2]=STARRED");
147 4 : for (int i = 0; i < count; i++) free(arr[i]);
148 1 : free(arr);
149 : }
150 :
151 1 : static void test_json_string_array_empty(void) {
152 1 : const char *j = "{\"labels\": []}";
153 1 : char **arr = NULL; int count = 0;
154 1 : ASSERT(json_get_string_array(j, "labels", &arr, &count) == 0, "str_array empty: ok");
155 1 : ASSERT(count == 0, "str_array empty: count=0");
156 1 : free(arr);
157 : }
158 :
159 1 : static void test_json_string_array_single(void) {
160 1 : const char *j = "{\"labels\": [\"INBOX\"]}";
161 1 : char **arr = NULL; int count = 0;
162 1 : ASSERT(json_get_string_array(j, "labels", &arr, &count) == 0, "str_array single: ok");
163 1 : ASSERT(count == 1, "str_array single: count=1");
164 1 : ASSERT(strcmp(arr[0], "INBOX") == 0, "str_array single: INBOX");
165 1 : free(arr[0]); free(arr);
166 : }
167 :
168 1 : static void test_json_string_array_not_found(void) {
169 1 : const char *j = "{\"x\": 1}";
170 1 : char **arr = NULL; int count = 0;
171 1 : ASSERT(json_get_string_array(j, "labels", &arr, &count) == -1, "str_array missing: -1");
172 : }
173 :
174 1 : static void test_json_string_array_with_escapes(void) {
175 1 : const char *j = "{\"v\": [\"a\\nb\", \"c\\td\"]}";
176 1 : char **arr = NULL; int count = 0;
177 1 : ASSERT(json_get_string_array(j, "v", &arr, &count) == 0, "str_array escape: ok");
178 1 : ASSERT(count == 2, "str_array escape: count=2");
179 1 : ASSERT(strcmp(arr[0], "a\nb") == 0, "str_array escape[0]");
180 1 : ASSERT(strcmp(arr[1], "c\td") == 0, "str_array escape[1]");
181 3 : for (int i = 0; i < count; i++) free(arr[i]);
182 1 : free(arr);
183 : }
184 :
185 : /* ── json_foreach_object ────────────────────────────────────────────── */
186 :
187 : struct obj_ctx {
188 : char ids[16][32];
189 : int count;
190 : };
191 :
192 5 : static void collect_id(const char *obj, int index, void *ctx) {
193 5 : struct obj_ctx *c = ctx;
194 : (void)index;
195 5 : char *id = json_get_string(obj, "id");
196 5 : if (id && c->count < 16) {
197 5 : strncpy(c->ids[c->count], id, 31);
198 5 : c->ids[c->count][31] = '\0';
199 5 : c->count++;
200 : }
201 5 : free(id);
202 5 : }
203 :
204 1 : static void test_json_foreach_simple(void) {
205 1 : const char *j = "{\"messages\": [{\"id\": \"abc\"}, {\"id\": \"def\"}]}";
206 1 : struct obj_ctx ctx = {.count = 0};
207 1 : int n = json_foreach_object(j, "messages", collect_id, &ctx);
208 1 : ASSERT(n == 2, "foreach: 2 objects");
209 1 : ASSERT(ctx.count == 2, "foreach: collected 2");
210 1 : ASSERT(strcmp(ctx.ids[0], "abc") == 0, "foreach[0]=abc");
211 1 : ASSERT(strcmp(ctx.ids[1], "def") == 0, "foreach[1]=def");
212 : }
213 :
214 1 : static void test_json_foreach_empty(void) {
215 1 : const char *j = "{\"messages\": []}";
216 1 : struct obj_ctx ctx = {.count = 0};
217 1 : int n = json_foreach_object(j, "messages", collect_id, &ctx);
218 1 : ASSERT(n == 0, "foreach empty: 0");
219 1 : ASSERT(ctx.count == 0, "foreach empty: none collected");
220 : }
221 :
222 1 : static void test_json_foreach_not_found(void) {
223 1 : const char *j = "{\"x\": 1}";
224 1 : struct obj_ctx ctx = {.count = 0};
225 1 : int n = json_foreach_object(j, "messages", collect_id, &ctx);
226 1 : ASSERT(n == -1, "foreach missing: -1");
227 : }
228 :
229 1 : static void test_json_foreach_nested_objects(void) {
230 1 : const char *j = "{\"items\": [{\"id\": \"a\", \"meta\": {\"x\": 1}}, {\"id\": \"b\"}]}";
231 1 : struct obj_ctx ctx = {.count = 0};
232 1 : int n = json_foreach_object(j, "items", collect_id, &ctx);
233 1 : ASSERT(n == 2, "foreach nested: 2");
234 1 : ASSERT(strcmp(ctx.ids[0], "a") == 0, "foreach nested[0]=a");
235 1 : ASSERT(strcmp(ctx.ids[1], "b") == 0, "foreach nested[1]=b");
236 : }
237 :
238 1 : static void test_json_foreach_single(void) {
239 1 : const char *j = "{\"items\": [{\"id\": \"only\"}]}";
240 1 : struct obj_ctx ctx = {.count = 0};
241 1 : int n = json_foreach_object(j, "items", collect_id, &ctx);
242 1 : ASSERT(n == 1, "foreach single: 1");
243 1 : ASSERT(strcmp(ctx.ids[0], "only") == 0, "foreach single[0]=only");
244 : }
245 :
246 : /* ── Additional branch coverage tests ──────────────────────────────── */
247 :
248 : /* Lines 56-61: string inside array during skip_value (array branch).
249 : * The key is NOT "v", so the array value must be skipped; the array
250 : * contains strings with escape sequences, exercising the string-skip
251 : * path inside the array branch of skip_value. */
252 1 : static void test_json_skip_array_with_strings(void) {
253 : /* "v" has an array of strings with escapes; we look up "other" → skip "v" */
254 1 : const char *j = "{\"v\": [\"a\\nb\", \"c\\td\"], \"other\": \"found\"}";
255 1 : char *v = json_get_string(j, "other");
256 1 : ASSERT(v != NULL, "skip_array_with_strings: found other key");
257 1 : ASSERT(strcmp(v, "found") == 0, "skip_array_with_strings: value matches");
258 1 : free(v);
259 : }
260 :
261 : /* Lines 128, 130, 132-133: unescape \/, \r, \b, \f */
262 1 : static void test_json_unescape_slash(void) {
263 1 : const char *j = "{\"msg\": \"a\\/b\"}";
264 1 : char *v = json_get_string(j, "msg");
265 1 : ASSERT(v != NULL, "unescape slash: found");
266 1 : ASSERT(strcmp(v, "a/b") == 0, "unescape slash: value");
267 1 : free(v);
268 : }
269 :
270 1 : static void test_json_unescape_carriage_return(void) {
271 1 : const char *j = "{\"msg\": \"a\\rb\"}";
272 1 : char *v = json_get_string(j, "msg");
273 1 : ASSERT(v != NULL, "unescape \\r: found");
274 1 : ASSERT(v[0] == 'a' && v[1] == '\r' && v[2] == 'b' && v[3] == '\0',
275 : "unescape \\r: value contains carriage return");
276 1 : free(v);
277 : }
278 :
279 1 : static void test_json_unescape_backspace(void) {
280 1 : const char *j = "{\"msg\": \"a\\bb\"}";
281 1 : char *v = json_get_string(j, "msg");
282 1 : ASSERT(v != NULL, "unescape \\b: found");
283 1 : ASSERT(v[0] == 'a' && v[1] == '\b' && v[2] == 'b' && v[3] == '\0',
284 : "unescape \\b: value contains backspace");
285 1 : free(v);
286 : }
287 :
288 1 : static void test_json_unescape_form_feed(void) {
289 1 : const char *j = "{\"msg\": \"a\\fb\"}";
290 1 : char *v = json_get_string(j, "msg");
291 1 : ASSERT(v != NULL, "unescape \\f: found");
292 1 : ASSERT(v[0] == 'a' && v[1] == '\f' && v[2] == 'b' && v[3] == '\0',
293 : "unescape \\f: value contains form feed");
294 1 : free(v);
295 : }
296 :
297 : /* Lines 142, 146-148: \uXXXX with non-ASCII code point → '?' placeholder */
298 1 : static void test_json_unescape_unicode_non_ascii(void) {
299 : /* \u00E9 = 0xE9 = 233, which is >= 0x80 → '?' placeholder */
300 1 : const char *j = "{\"msg\": \"a\\u00E9b\"}";
301 1 : char *v = json_get_string(j, "msg");
302 1 : ASSERT(v != NULL, "unescape unicode non-ASCII: found");
303 1 : ASSERT(v[0] == 'a' && v[1] == '?' && v[2] == 'b' && v[3] == '\0',
304 : "unescape unicode non-ASCII: '?' placeholder");
305 1 : free(v);
306 : }
307 :
308 : /* Lines 146-148: unescape default case — unknown escape char passes through */
309 1 : static void test_json_unescape_unknown_escape(void) {
310 : /* \z is not a recognised escape sequence; the default branch emits 'z' */
311 1 : const char *j = "{\"msg\": \"a\\zb\"}";
312 1 : char *v = json_get_string(j, "msg");
313 1 : ASSERT(v != NULL, "unescape unknown escape: found");
314 1 : ASSERT(v[0] == 'a' && v[1] == 'z' && v[2] == 'b' && v[3] == '\0',
315 : "unescape unknown escape: passes through raw char");
316 1 : free(v);
317 : }
318 :
319 : /* Line 207: json_get_int returns -1 when value is not a number */
320 1 : static void test_json_get_int_non_number_value(void) {
321 1 : const char *j = "{\"x\": true}";
322 1 : int v = 0;
323 1 : ASSERT(json_get_int(j, "x", &v) == -1,
324 : "json_get_int non-number: returns -1 for boolean value");
325 : }
326 :
327 : /* Lines 244-248: json_get_string_array realloc when count >= cap.
328 : * cap starts at 8 in the source; use 20 strings to trigger realloc. */
329 1 : static void test_json_string_array_realloc(void) {
330 : /* Build a JSON array with 20 strings */
331 : char j[1024];
332 1 : int pos = 0;
333 1 : pos += snprintf(j + pos, sizeof(j) - (size_t)pos, "{\"items\": [");
334 21 : for (int i = 0; i < 20; i++) {
335 20 : pos += snprintf(j + pos, sizeof(j) - (size_t)pos,
336 : "%s\"item%d\"", i ? "," : "", i);
337 : }
338 1 : pos += snprintf(j + pos, sizeof(j) - (size_t)pos, "]}");
339 :
340 1 : char **arr = NULL; int count = 0;
341 1 : ASSERT(json_get_string_array(j, "items", &arr, &count) == 0,
342 : "str_array realloc: ok");
343 1 : ASSERT(count == 20, "str_array realloc: count=20");
344 1 : if (arr) {
345 1 : ASSERT(strcmp(arr[0], "item0") == 0, "str_array realloc: arr[0]=item0");
346 1 : ASSERT(strcmp(arr[19], "item19") == 0, "str_array realloc: arr[19]=item19");
347 21 : for (int i = 0; i < count; i++) free(arr[i]);
348 1 : free(arr);
349 : }
350 : }
351 :
352 : /* ── Registration ───────────────────────────────────────────────────── */
353 :
354 1 : void run_json_util_tests(void) {
355 : /* json_get_string */
356 1 : RUN_TEST(test_json_get_string_simple);
357 1 : RUN_TEST(test_json_get_string_second_key);
358 1 : RUN_TEST(test_json_get_string_not_found);
359 1 : RUN_TEST(test_json_get_string_escape);
360 1 : RUN_TEST(test_json_get_string_escaped_quote);
361 1 : RUN_TEST(test_json_get_string_skip_nested_object);
362 1 : RUN_TEST(test_json_get_string_skip_nested_array);
363 1 : RUN_TEST(test_json_get_string_null_input);
364 1 : RUN_TEST(test_json_get_string_empty_object);
365 1 : RUN_TEST(test_json_get_string_whitespace);
366 1 : RUN_TEST(test_json_get_string_empty_value);
367 1 : RUN_TEST(test_json_get_string_unicode_escape);
368 1 : RUN_TEST(test_json_get_string_backslash);
369 :
370 : /* json_get_int */
371 1 : RUN_TEST(test_json_get_int_simple);
372 1 : RUN_TEST(test_json_get_int_negative);
373 1 : RUN_TEST(test_json_get_int_quoted);
374 1 : RUN_TEST(test_json_get_int_not_found);
375 1 : RUN_TEST(test_json_get_int_null_input);
376 :
377 : /* json_get_string_array */
378 1 : RUN_TEST(test_json_string_array_simple);
379 1 : RUN_TEST(test_json_string_array_empty);
380 1 : RUN_TEST(test_json_string_array_single);
381 1 : RUN_TEST(test_json_string_array_not_found);
382 1 : RUN_TEST(test_json_string_array_with_escapes);
383 :
384 : /* json_foreach_object */
385 1 : RUN_TEST(test_json_foreach_simple);
386 1 : RUN_TEST(test_json_foreach_empty);
387 1 : RUN_TEST(test_json_foreach_not_found);
388 1 : RUN_TEST(test_json_foreach_nested_objects);
389 1 : RUN_TEST(test_json_foreach_single);
390 :
391 : /* additional branch coverage */
392 1 : RUN_TEST(test_json_skip_array_with_strings);
393 1 : RUN_TEST(test_json_unescape_slash);
394 1 : RUN_TEST(test_json_unescape_carriage_return);
395 1 : RUN_TEST(test_json_unescape_backspace);
396 1 : RUN_TEST(test_json_unescape_form_feed);
397 1 : RUN_TEST(test_json_unescape_unicode_non_ascii);
398 1 : RUN_TEST(test_json_unescape_unknown_escape);
399 1 : RUN_TEST(test_json_get_int_non_number_value);
400 1 : RUN_TEST(test_json_string_array_realloc);
401 1 : }
|