LCOV - code coverage report
Current view: top level - tests/unit - test_json_util.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 267 267
Test Date: 2026-05-07 15:53:07 Functions: 100.0 % 39 39

            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 : }
        

Generated by: LCOV version 2.0-1