LCOV - code coverage report
Current view: top level - tests/unit - test_arg_parse.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 391 391
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 68 68

            Line data    Source code
       1              : /**
       2              :  * @file test_arg_parse.c
       3              :  * @brief Unit tests for arg_parse.c.
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "arg_parse.h"
       8              : 
       9              : #include <string.h>
      10              : #include <stdlib.h>
      11              : #include <unistd.h>
      12              : #include <fcntl.h>
      13              : 
      14              : /* ---- Test: no arguments → interactive mode (CMD_NONE) ---- */
      15            1 : static void test_no_args(void) {
      16            1 :     char *argv[] = {"tg-cli", NULL};
      17              :     ArgResult r;
      18            1 :     int rc = arg_parse(1, argv, &r);
      19            1 :     ASSERT(rc == ARG_OK,        "no args: must return ARG_OK");
      20            1 :     ASSERT(r.command == CMD_NONE, "no args: command must be CMD_NONE");
      21            1 :     ASSERT(r.json  == 0,        "no args: json must be 0");
      22            1 :     ASSERT(r.quiet == 0,        "no args: quiet must be 0");
      23              : }
      24              : 
      25              : /* ---- Test: --help / -h ---- */
      26            1 : static void test_help_flag(void) {
      27            1 :     char *argv1[] = {"tg-cli", "--help", NULL};
      28              :     ArgResult r;
      29            1 :     ASSERT(arg_parse(2, argv1, &r) == ARG_HELP, "--help: must return ARG_HELP");
      30              : 
      31            1 :     char *argv2[] = {"tg-cli", "-h", NULL};
      32            1 :     ASSERT(arg_parse(2, argv2, &r) == ARG_HELP, "-h: must return ARG_HELP");
      33              : 
      34            1 :     char *argv3[] = {"tg-cli", "help", NULL};
      35            1 :     ASSERT(arg_parse(2, argv3, &r) == ARG_HELP, "'help' subcommand must return ARG_HELP");
      36              : }
      37              : 
      38              : /* ---- Test: --version / -v ---- */
      39            1 : static void test_version_flag(void) {
      40            1 :     char *argv1[] = {"tg-cli", "--version", NULL};
      41              :     ArgResult r;
      42            1 :     ASSERT(arg_parse(2, argv1, &r) == ARG_VERSION, "--version: must return ARG_VERSION");
      43              : 
      44            1 :     char *argv2[] = {"tg-cli", "-v", NULL};
      45            1 :     ASSERT(arg_parse(2, argv2, &r) == ARG_VERSION, "-v: must return ARG_VERSION");
      46              : 
      47            1 :     char *argv3[] = {"tg-cli", "version", NULL};
      48            1 :     ASSERT(arg_parse(2, argv3, &r) == ARG_VERSION, "'version' subcommand must return ARG_VERSION");
      49              : }
      50              : 
      51              : /* ---- Test: global flags set correctly ---- */
      52            1 : static void test_global_flags(void) {
      53            1 :     char *argv[] = {"tg-cli", "--json", "--quiet", "contacts", NULL};
      54              :     ArgResult r;
      55            1 :     int rc = arg_parse(4, argv, &r);
      56            1 :     ASSERT(rc == ARG_OK,              "global flags: must return ARG_OK");
      57            1 :     ASSERT(r.json  == 1,              "global flags: json must be 1");
      58            1 :     ASSERT(r.quiet == 1,              "global flags: quiet must be 1");
      59            1 :     ASSERT(r.command == CMD_CONTACTS, "global flags: command must be CMD_CONTACTS");
      60              : }
      61              : 
      62              : /* ---- Test: --config <path> ---- */
      63            1 : static void test_config_flag(void) {
      64            1 :     char *argv[] = {"tg-cli", "--config", "/tmp/my.ini", "contacts", NULL};
      65              :     ArgResult r;
      66            1 :     int rc = arg_parse(4, argv, &r);
      67            1 :     ASSERT(rc == ARG_OK,                          "--config: must return ARG_OK");
      68            1 :     ASSERT(r.config_path != NULL,                 "--config: config_path must be set");
      69            1 :     ASSERT(strcmp(r.config_path, "/tmp/my.ini") == 0,
      70              :            "--config: config_path must match");
      71              : }
      72              : 
      73              : /* ---- Test: --config without value → error ---- */
      74            1 : static void test_config_flag_missing_value(void) {
      75            1 :     char *argv[] = {"tg-cli", "--config", NULL};
      76              :     ArgResult r;
      77            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
      78              :            "--config without value: must return ARG_ERROR");
      79              : }
      80              : 
      81              : /* ---- Test: dialogs (no options) ---- */
      82            1 : static void test_dialogs_no_options(void) {
      83            1 :     char *argv[] = {"tg-cli", "dialogs", NULL};
      84              :     ArgResult r;
      85            1 :     int rc = arg_parse(2, argv, &r);
      86            1 :     ASSERT(rc == ARG_OK,               "dialogs: must return ARG_OK");
      87            1 :     ASSERT(r.command == CMD_DIALOGS,   "dialogs: command must be CMD_DIALOGS");
      88            1 :     ASSERT(r.limit == 20,              "dialogs: default limit must be 20");
      89              : }
      90              : 
      91              : /* ---- Test: dialogs --limit N ---- */
      92            1 : static void test_dialogs_with_limit(void) {
      93            1 :     char *argv[] = {"tg-cli", "dialogs", "--limit", "100", NULL};
      94              :     ArgResult r;
      95            1 :     int rc = arg_parse(4, argv, &r);
      96            1 :     ASSERT(rc == ARG_OK,            "dialogs --limit: must return ARG_OK");
      97            1 :     ASSERT(r.command == CMD_DIALOGS, "dialogs --limit: command must be CMD_DIALOGS");
      98            1 :     ASSERT(r.limit == 100,           "dialogs --limit: limit must be 100");
      99              : }
     100              : 
     101              : /* ---- Test: dialogs --limit missing value → error ---- */
     102            1 : static void test_dialogs_limit_missing(void) {
     103            1 :     char *argv[] = {"tg-cli", "dialogs", "--limit", NULL};
     104              :     ArgResult r;
     105            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_ERROR,
     106              :            "dialogs --limit without value: must return ARG_ERROR");
     107              : }
     108              : 
     109              : /* ---- Test: history <peer> ---- */
     110            1 : static void test_history_basic(void) {
     111            1 :     char *argv[] = {"tg-cli", "history", "username123", NULL};
     112              :     ArgResult r;
     113            1 :     int rc = arg_parse(3, argv, &r);
     114            1 :     ASSERT(rc == ARG_OK,             "history: must return ARG_OK");
     115            1 :     ASSERT(r.command == CMD_HISTORY, "history: command must be CMD_HISTORY");
     116            1 :     ASSERT(r.peer != NULL,           "history: peer must be set");
     117            1 :     ASSERT(strcmp(r.peer, "username123") == 0, "history: peer must match");
     118            1 :     ASSERT(r.limit  == 50,           "history: default limit must be 50");
     119            1 :     ASSERT(r.offset == 0,            "history: default offset must be 0");
     120              : }
     121              : 
     122              : /* ---- Test: history <peer> --limit N --offset M ---- */
     123            1 : static void test_history_with_options(void) {
     124            1 :     char *argv[] = {"tg-cli", "history", "@testchat",
     125              :                     "--limit", "30", "--offset", "60", NULL};
     126              :     ArgResult r;
     127            1 :     int rc = arg_parse(7, argv, &r);
     128            1 :     ASSERT(rc == ARG_OK,             "history opts: must return ARG_OK");
     129            1 :     ASSERT(r.command == CMD_HISTORY, "history opts: command must be CMD_HISTORY");
     130            1 :     ASSERT(strcmp(r.peer, "@testchat") == 0, "history opts: peer must match");
     131            1 :     ASSERT(r.limit  == 30,           "history opts: limit must be 30");
     132            1 :     ASSERT(r.offset == 60,           "history opts: offset must be 60");
     133              : }
     134              : 
     135              : /* ---- Test: history without peer → error ---- */
     136            1 : static void test_history_missing_peer(void) {
     137            1 :     char *argv[] = {"tg-cli", "history", NULL};
     138              :     ArgResult r;
     139            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
     140              :            "history without peer: must return ARG_ERROR");
     141              : }
     142              : 
     143              : /* ---- Test: send <peer> <message> ---- */
     144            1 : static void test_send_basic(void) {
     145            1 :     char *argv[] = {"tg-cli", "send", "@user", "Hello, world!", NULL};
     146              :     ArgResult r;
     147            1 :     int rc = arg_parse(4, argv, &r);
     148            1 :     ASSERT(rc == ARG_OK,          "send: must return ARG_OK");
     149            1 :     ASSERT(r.command == CMD_SEND, "send: command must be CMD_SEND");
     150            1 :     ASSERT(strcmp(r.peer,    "@user")        == 0, "send: peer must match");
     151            1 :     ASSERT(strcmp(r.message, "Hello, world!") == 0, "send: message must match");
     152              : }
     153              : 
     154              : /* ---- Test: send without peer → error ---- */
     155            1 : static void test_send_missing_peer(void) {
     156            1 :     char *argv[] = {"tg-cli", "send", NULL};
     157              :     ArgResult r;
     158            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
     159              :            "send without peer: must return ARG_ERROR");
     160              : }
     161              : 
     162              : /* ---- Test: send without message → ARG_OK, message NULL (stdin pipe path).
     163              :  *
     164              :  * Since P8-03 the send parser accepts <peer> alone and defers message
     165              :  * sourcing to tg-cli (which reads stdin when the tty is not connected).
     166              :  */
     167            1 : static void test_send_missing_message(void) {
     168            1 :     char *argv[] = {"tg-cli", "send", "@user", NULL};
     169              :     ArgResult r;
     170            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_OK,
     171              :            "send without message: allowed for stdin pipe");
     172            1 :     ASSERT(r.message == NULL,
     173              :            "send without message: message stays NULL so tg-cli can read stdin");
     174              : }
     175              : 
     176              : /* ---- Test: search <query> (no peer) ---- */
     177            1 : static void test_search_query_only(void) {
     178            1 :     char *argv[] = {"tg-cli", "search", "hello world", NULL};
     179              :     ArgResult r;
     180            1 :     int rc = arg_parse(3, argv, &r);
     181            1 :     ASSERT(rc == ARG_OK,            "search: must return ARG_OK");
     182            1 :     ASSERT(r.command == CMD_SEARCH, "search: command must be CMD_SEARCH");
     183            1 :     ASSERT(r.peer == NULL,          "search (no peer): peer must be NULL");
     184            1 :     ASSERT(strcmp(r.query, "hello world") == 0, "search: query must match");
     185              : }
     186              : 
     187              : /* ---- Test: search <peer> <query> ---- */
     188            1 : static void test_search_peer_and_query(void) {
     189            1 :     char *argv[] = {"tg-cli", "search", "@news", "breaking", NULL};
     190              :     ArgResult r;
     191            1 :     int rc = arg_parse(4, argv, &r);
     192            1 :     ASSERT(rc == ARG_OK,            "search peer+query: must return ARG_OK");
     193            1 :     ASSERT(r.command == CMD_SEARCH, "search peer+query: command must be CMD_SEARCH");
     194            1 :     ASSERT(strcmp(r.peer,  "@news")    == 0, "search peer+query: peer must match");
     195            1 :     ASSERT(strcmp(r.query, "breaking") == 0, "search peer+query: query must match");
     196              : }
     197              : 
     198              : /* ---- Test: search without query → error ---- */
     199            1 : static void test_search_missing_query(void) {
     200            1 :     char *argv[] = {"tg-cli", "search", NULL};
     201              :     ArgResult r;
     202            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
     203              :            "search without query: must return ARG_ERROR");
     204              : }
     205              : 
     206              : /* ---- Test: contacts ---- */
     207            1 : static void test_contacts(void) {
     208            1 :     char *argv[] = {"tg-cli", "contacts", NULL};
     209              :     ArgResult r;
     210            1 :     int rc = arg_parse(2, argv, &r);
     211            1 :     ASSERT(rc == ARG_OK,              "contacts: must return ARG_OK");
     212            1 :     ASSERT(r.command == CMD_CONTACTS, "contacts: command must be CMD_CONTACTS");
     213              : }
     214              : 
     215              : /* ---- Test: user-info <peer> ---- */
     216            1 : static void test_user_info(void) {
     217            1 :     char *argv[] = {"tg-cli", "user-info", "durov", NULL};
     218              :     ArgResult r;
     219            1 :     int rc = arg_parse(3, argv, &r);
     220            1 :     ASSERT(rc == ARG_OK,               "user-info: must return ARG_OK");
     221            1 :     ASSERT(r.command == CMD_USER_INFO, "user-info: command must be CMD_USER_INFO");
     222            1 :     ASSERT(strcmp(r.peer, "durov") == 0, "user-info: peer must match");
     223              : }
     224              : 
     225              : /* ---- Test: user-info without peer → error ---- */
     226            1 : static void test_user_info_missing_peer(void) {
     227            1 :     char *argv[] = {"tg-cli", "user-info", NULL};
     228              :     ArgResult r;
     229            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
     230              :            "user-info without peer: must return ARG_ERROR");
     231              : }
     232              : 
     233              : /* ---- Test: unknown subcommand → error ---- */
     234            1 : static void test_unknown_subcommand(void) {
     235            1 :     char *argv[] = {"tg-cli", "frobniculate", NULL};
     236              :     ArgResult r;
     237            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
     238              :            "unknown subcommand: must return ARG_ERROR");
     239              : }
     240              : 
     241              : /* ---- Test: null argv/out → error ---- */
     242            1 : static void test_null_args(void) {
     243              :     ArgResult r;
     244            1 :     ASSERT(arg_parse(0, NULL, &r) == ARG_ERROR, "null argv: must return ARG_ERROR");
     245            1 :     ASSERT(arg_parse(1, (char *[]){NULL}, NULL)  == ARG_ERROR, "null out: must return ARG_ERROR");
     246              : }
     247              : 
     248              : /* ---- Test: dialogs --limit non-numeric value ---- */
     249            1 : static void test_dialogs_limit_non_numeric(void) {
     250            1 :     char *argv[] = {"tg-cli", "dialogs", "--limit", "abc", NULL};
     251              :     ArgResult r;
     252            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     253              :            "dialogs --limit abc: must return ARG_ERROR");
     254              : }
     255              : 
     256              : /* ---- Test: dialogs unknown option ---- */
     257            1 : static void test_dialogs_unknown_option(void) {
     258            1 :     char *argv[] = {"tg-cli", "dialogs", "--bogus", NULL};
     259              :     ArgResult r;
     260            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_ERROR,
     261              :            "dialogs --bogus: must return ARG_ERROR");
     262              : }
     263              : 
     264              : /* ---- Test: history with peer starting with '-' ---- */
     265            1 : static void test_history_dash_peer(void) {
     266            1 :     char *argv[] = {"tg-cli", "history", "-notapeer", NULL};
     267              :     ArgResult r;
     268            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_ERROR,
     269              :            "history -peer: must return ARG_ERROR");
     270              : }
     271              : 
     272              : /* ---- Test: history --limit missing value ---- */
     273            1 : static void test_history_limit_missing(void) {
     274            1 :     char *argv[] = {"tg-cli", "history", "@u", "--limit", NULL};
     275              :     ArgResult r;
     276            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     277              :            "history --limit w/o val: ARG_ERROR");
     278              : }
     279              : 
     280              : /* ---- Test: history --limit non-numeric ---- */
     281            1 : static void test_history_limit_non_numeric(void) {
     282            1 :     char *argv[] = {"tg-cli", "history", "@u", "--limit", "xyz", NULL};
     283              :     ArgResult r;
     284            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     285              :            "history --limit xyz: ARG_ERROR");
     286              : }
     287              : 
     288              : /* ---- Test: history --offset missing value ---- */
     289            1 : static void test_history_offset_missing(void) {
     290            1 :     char *argv[] = {"tg-cli", "history", "@u", "--offset", NULL};
     291              :     ArgResult r;
     292            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     293              :            "history --offset w/o val: ARG_ERROR");
     294              : }
     295              : 
     296              : /* ---- Test: history --offset non-numeric ---- */
     297            1 : static void test_history_offset_non_numeric(void) {
     298            1 :     char *argv[] = {"tg-cli", "history", "@u", "--offset", "nope", NULL};
     299              :     ArgResult r;
     300            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     301              :            "history --offset nope: ARG_ERROR");
     302              : }
     303              : 
     304              : /* ---- Test: history unknown option ---- */
     305            1 : static void test_history_unknown_option(void) {
     306            1 :     char *argv[] = {"tg-cli", "history", "@u", "--weird", NULL};
     307              :     ArgResult r;
     308            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     309              :            "history --weird: ARG_ERROR");
     310              : }
     311              : 
     312              : /* ---- Test: send with peer starting with '-' ---- */
     313            1 : static void test_send_dash_peer(void) {
     314            1 :     char *argv[] = {"tg-cli", "send", "-peer", "msg", NULL};
     315              :     ArgResult r;
     316            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     317              :            "send -peer: must return ARG_ERROR");
     318              : }
     319              : 
     320              : /* ---- Test: search with all-dash args ---- */
     321            1 : static void test_search_all_dash(void) {
     322            1 :     char *argv[] = {"tg-cli", "search", "-flag", NULL};
     323              :     ArgResult r;
     324            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_ERROR,
     325              :            "search -flag: must return ARG_ERROR");
     326              : }
     327              : 
     328              : /* ---- Test: --json without subcommand → error ---- */
     329            1 : static void test_batch_no_subcommand(void) {
     330            1 :     char *argv[] = {"tg-cli", "--json", NULL};
     331              :     ArgResult r;
     332            1 :     ASSERT(arg_parse(2, argv, &r) == ARG_ERROR,
     333              :            "--json w/o subcommand: ARG_ERROR");
     334              : }
     335              : 
     336              : /* ---- Test: --json before subcommand ---- */
     337            1 : static void test_json_before_subcommand(void) {
     338            1 :     char *argv[] = {"tg-cli", "--json", "dialogs", NULL};
     339              :     ArgResult r;
     340            1 :     int rc = arg_parse(3, argv, &r);
     341            1 :     ASSERT(rc == ARG_OK,             "--json before dialogs: must return ARG_OK");
     342            1 :     ASSERT(r.json == 1,              "--json before dialogs: json must be 1");
     343            1 :     ASSERT(r.command == CMD_DIALOGS, "--json before dialogs: command must be CMD_DIALOGS");
     344              : }
     345              : 
     346              : /* ---- Test: dialogs --archived ---- */
     347            1 : static void test_dialogs_archived(void) {
     348            1 :     char *argv[] = {"tg-cli", "dialogs", "--archived", NULL};
     349              :     ArgResult r;
     350            1 :     int rc = arg_parse(3, argv, &r);
     351            1 :     ASSERT(rc == ARG_OK,             "dialogs --archived: must return ARG_OK");
     352            1 :     ASSERT(r.command == CMD_DIALOGS, "dialogs --archived: CMD_DIALOGS");
     353            1 :     ASSERT(r.archived == 1,          "dialogs --archived: archived flag set");
     354              : }
     355              : 
     356              : /* ---- Test: dialogs --archived --limit N (combined) ---- */
     357            1 : static void test_dialogs_archived_with_limit(void) {
     358            1 :     char *argv[] = {"tg-cli", "dialogs", "--archived", "--limit", "50", NULL};
     359              :     ArgResult r;
     360            1 :     int rc = arg_parse(5, argv, &r);
     361            1 :     ASSERT(rc == ARG_OK,             "dialogs --archived --limit: ARG_OK");
     362            1 :     ASSERT(r.archived == 1,          "archived flag set");
     363            1 :     ASSERT(r.limit == 50,            "limit=50");
     364              : }
     365              : 
     366              : /* ---- Test: dialogs (no --archived) → archived is 0 ---- */
     367            1 : static void test_dialogs_not_archived_by_default(void) {
     368            1 :     char *argv[] = {"tg-cli", "dialogs", NULL};
     369              :     ArgResult r;
     370            1 :     arg_parse(2, argv, &r);
     371            1 :     ASSERT(r.archived == 0, "dialogs: archived must default to 0");
     372              : }
     373              : 
     374              : /* ---- Test: watch --interval in-range value ---- */
     375            1 : static void test_watch_interval_valid(void) {
     376            1 :     char *argv[] = {"tg-cli", "watch", "--interval", "5", NULL};
     377              :     ArgResult r;
     378            1 :     int rc = arg_parse(4, argv, &r);
     379            1 :     ASSERT(rc == ARG_OK,             "watch --interval 5: must return ARG_OK");
     380            1 :     ASSERT(r.command == CMD_WATCH,   "watch --interval 5: CMD_WATCH");
     381            1 :     ASSERT(r.watch_interval == 5,    "watch --interval 5: interval must be 5");
     382              : }
     383              : 
     384              : /* ---- Test: watch --interval boundary values ---- */
     385            1 : static void test_watch_interval_boundaries(void) {
     386              :     ArgResult r;
     387            1 :     char *argv_min[] = {"tg-cli", "watch", "--interval", "2", NULL};
     388            1 :     ASSERT(arg_parse(4, argv_min, &r) == ARG_OK,
     389              :            "watch --interval 2 (min): ARG_OK");
     390            1 :     ASSERT(r.watch_interval == 2, "watch --interval 2: interval==2");
     391              : 
     392            1 :     char *argv_max[] = {"tg-cli", "watch", "--interval", "3600", NULL};
     393            1 :     ASSERT(arg_parse(4, argv_max, &r) == ARG_OK,
     394              :            "watch --interval 3600 (max): ARG_OK");
     395            1 :     ASSERT(r.watch_interval == 3600, "watch --interval 3600: interval==3600");
     396              : }
     397              : 
     398              : /* ---- Test: watch --interval below range → error ---- */
     399            1 : static void test_watch_interval_too_low(void) {
     400            1 :     char *argv[] = {"tg-cli", "watch", "--interval", "1", NULL};
     401              :     ArgResult r;
     402            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     403              :            "watch --interval 1: must return ARG_ERROR (below range)");
     404              : }
     405              : 
     406              : /* ---- Test: watch --interval above range → error ---- */
     407            1 : static void test_watch_interval_too_high(void) {
     408            1 :     char *argv[] = {"tg-cli", "watch", "--interval", "3601", NULL};
     409              :     ArgResult r;
     410            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     411              :            "watch --interval 3601: must return ARG_ERROR (above range)");
     412              : }
     413              : 
     414              : /* ---- Test: watch --interval non-numeric → error ---- */
     415            1 : static void test_watch_interval_non_numeric(void) {
     416            1 :     char *argv[] = {"tg-cli", "watch", "--interval", "fast", NULL};
     417              :     ArgResult r;
     418            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     419              :            "watch --interval fast: must return ARG_ERROR");
     420              : }
     421              : 
     422              : /* ---- Test: watch --interval missing value → error ---- */
     423            1 : static void test_watch_interval_missing_value(void) {
     424            1 :     char *argv[] = {"tg-cli", "watch", "--interval", NULL};
     425              :     ArgResult r;
     426            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_ERROR,
     427              :            "watch --interval w/o value: must return ARG_ERROR");
     428              : }
     429              : 
     430              : /* ---- Test: watch default interval is 30 ---- */
     431            1 : static void test_watch_default_interval(void) {
     432            1 :     char *argv[] = {"tg-cli", "watch", NULL};
     433              :     ArgResult r;
     434            1 :     int rc = arg_parse(2, argv, &r);
     435            1 :     ASSERT(rc == ARG_OK,               "watch (no options): ARG_OK");
     436            1 :     ASSERT(r.watch_interval == 30,     "watch: default interval must be 30");
     437              : }
     438              : 
     439              : /* ---- Test: history --no-media sets flag ---- */
     440            1 : static void test_history_no_media_flag(void) {
     441            1 :     char *argv[] = {"tg-cli", "history", "@peer", "--no-media", NULL};
     442              :     ArgResult r;
     443            1 :     int rc = arg_parse(4, argv, &r);
     444            1 :     ASSERT(rc == ARG_OK,             "history --no-media: must return ARG_OK");
     445            1 :     ASSERT(r.command == CMD_HISTORY, "history --no-media: CMD_HISTORY");
     446            1 :     ASSERT(r.no_media == 1,          "history --no-media: no_media flag set");
     447              : }
     448              : 
     449              : /* ---- Test: history without --no-media → no_media is 0 ---- */
     450            1 : static void test_history_no_media_default_zero(void) {
     451            1 :     char *argv[] = {"tg-cli", "history", "@peer", NULL};
     452              :     ArgResult r;
     453            1 :     int rc = arg_parse(3, argv, &r);
     454            1 :     ASSERT(rc == ARG_OK,             "history (no flag): must return ARG_OK");
     455            1 :     ASSERT(r.no_media == 0,          "history: no_media must default to 0");
     456              : }
     457              : 
     458              : /* ---- Test: search default limit is 20 ---- */
     459            1 : static void test_search_default_limit(void) {
     460            1 :     char *argv[] = {"tg-cli", "search", "hello", NULL};
     461              :     ArgResult r;
     462            1 :     int rc = arg_parse(3, argv, &r);
     463            1 :     ASSERT(rc == ARG_OK,            "search default limit: ARG_OK");
     464            1 :     ASSERT(r.command == CMD_SEARCH, "search default limit: CMD_SEARCH");
     465            1 :     ASSERT(r.limit == 20,           "search default limit: limit must be 20");
     466              : }
     467              : 
     468              : /* ---- Test: search --limit N parses correctly ---- */
     469            1 : static void test_search_with_limit(void) {
     470            1 :     char *argv[] = {"tg-cli", "search", "hello", "--limit", "50", NULL};
     471              :     ArgResult r;
     472            1 :     int rc = arg_parse(5, argv, &r);
     473            1 :     ASSERT(rc == ARG_OK,            "search --limit 50: ARG_OK");
     474            1 :     ASSERT(r.command == CMD_SEARCH, "search --limit 50: CMD_SEARCH");
     475            1 :     ASSERT(r.limit == 50,           "search --limit 50: limit must be 50");
     476            1 :     ASSERT(strcmp(r.query, "hello") == 0, "search --limit 50: query must match");
     477              : }
     478              : 
     479              : /* ---- Test: search --limit 0 (below range) → error ---- */
     480            1 : static void test_search_limit_too_low(void) {
     481            1 :     char *argv[] = {"tg-cli", "search", "q", "--limit", "0", NULL};
     482              :     ArgResult r;
     483            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     484              :            "search --limit 0: must return ARG_ERROR (below range)");
     485              : }
     486              : 
     487              : /* ---- Test: search --limit 101 (above range) → error ---- */
     488            1 : static void test_search_limit_too_high(void) {
     489            1 :     char *argv[] = {"tg-cli", "search", "q", "--limit", "101", NULL};
     490              :     ArgResult r;
     491            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     492              :            "search --limit 101: must return ARG_ERROR (above range)");
     493              : }
     494              : 
     495              : /* ---- Test: search --limit boundary values (1 and 100) ---- */
     496            1 : static void test_search_limit_boundaries(void) {
     497              :     ArgResult r;
     498            1 :     char *argv_min[] = {"tg-cli", "search", "q", "--limit", "1", NULL};
     499            1 :     ASSERT(arg_parse(5, argv_min, &r) == ARG_OK,
     500              :            "search --limit 1 (min): ARG_OK");
     501            1 :     ASSERT(r.limit == 1, "search --limit 1: limit must be 1");
     502              : 
     503            1 :     char *argv_max[] = {"tg-cli", "search", "q", "--limit", "100", NULL};
     504            1 :     ASSERT(arg_parse(5, argv_max, &r) == ARG_OK,
     505              :            "search --limit 100 (max): ARG_OK");
     506            1 :     ASSERT(r.limit == 100, "search --limit 100: limit must be 100");
     507              : }
     508              : 
     509              : /* ---- Test: search <peer> <query> --limit N ---- */
     510            1 : static void test_search_peer_query_limit(void) {
     511            1 :     char *argv[] = {"tg-cli", "search", "@chan", "news", "--limit", "30", NULL};
     512              :     ArgResult r;
     513            1 :     int rc = arg_parse(6, argv, &r);
     514            1 :     ASSERT(rc == ARG_OK,                       "search peer+query+limit: ARG_OK");
     515            1 :     ASSERT(r.command == CMD_SEARCH,            "search peer+query+limit: CMD_SEARCH");
     516            1 :     ASSERT(strcmp(r.peer,  "@chan") == 0,      "search peer+query+limit: peer must match");
     517            1 :     ASSERT(strcmp(r.query, "news") == 0,       "search peer+query+limit: query must match");
     518            1 :     ASSERT(r.limit == 30,                      "search peer+query+limit: limit must be 30");
     519              : }
     520              : 
     521              : /* ---- FEAT-14: watch --peers parses into watch_peers ---- */
     522            1 : static void test_watch_peers_single(void) {
     523            1 :     char *argv[] = {"tg-cli", "watch", "--peers", "@chan", NULL};
     524              :     ArgResult r;
     525            1 :     int rc = arg_parse(4, argv, &r);
     526            1 :     ASSERT(rc == ARG_OK,                    "watch --peers single: ARG_OK");
     527            1 :     ASSERT(r.command == CMD_WATCH,          "watch --peers single: CMD_WATCH");
     528            1 :     ASSERT(r.watch_peers != NULL,           "watch --peers single: watch_peers not NULL");
     529            1 :     ASSERT(strcmp(r.watch_peers, "@chan") == 0,
     530              :            "watch --peers single: watch_peers == '@chan'");
     531            1 :     ASSERT(r.peer == NULL,                  "watch --peers single: peer must be NULL");
     532              : }
     533              : 
     534              : /* ---- FEAT-14: watch --peers with comma-separated list ---- */
     535            1 : static void test_watch_peers_multi(void) {
     536            1 :     char *argv[] = {"tg-cli", "watch", "--peers", "@a,111,@b", NULL};
     537              :     ArgResult r;
     538            1 :     int rc = arg_parse(4, argv, &r);
     539            1 :     ASSERT(rc == ARG_OK,                         "watch --peers multi: ARG_OK");
     540            1 :     ASSERT(r.watch_peers != NULL,                "watch --peers multi: watch_peers set");
     541            1 :     ASSERT(strcmp(r.watch_peers, "@a,111,@b") == 0,
     542              :            "watch --peers multi: raw value preserved");
     543              : }
     544              : 
     545              : /* ---- FEAT-14: watch without --peers → watch_peers is NULL ---- */
     546            1 : static void test_watch_no_peers(void) {
     547            1 :     char *argv[] = {"tg-cli", "watch", NULL};
     548              :     ArgResult r;
     549            1 :     int rc = arg_parse(2, argv, &r);
     550            1 :     ASSERT(rc == ARG_OK,            "watch no --peers: ARG_OK");
     551            1 :     ASSERT(r.watch_peers == NULL,   "watch no --peers: watch_peers must be NULL");
     552              : }
     553              : 
     554              : /* ---- FEAT-14: watch --peers missing value → error ---- */
     555            1 : static void test_watch_peers_missing_value(void) {
     556            1 :     char *argv[] = {"tg-cli", "watch", "--peers", NULL};
     557              :     ArgResult r;
     558            1 :     ASSERT(arg_parse(3, argv, &r) == ARG_ERROR,
     559              :            "watch --peers w/o value: must return ARG_ERROR");
     560              : }
     561              : 
     562              : /* ---- Test: upload (alias for send-file) ---- */
     563            1 : static void test_upload_alias(void) {
     564            1 :     char *argv[] = {"tg-cli", "upload", "@user", "/tmp/file.txt", NULL};
     565              :     ArgResult r;
     566            1 :     int rc = arg_parse(4, argv, &r);
     567            1 :     ASSERT(rc == ARG_OK,                 "upload alias: must return ARG_OK");
     568            1 :     ASSERT(r.command == CMD_SEND_FILE,   "upload alias: command must be CMD_SEND_FILE");
     569            1 :     ASSERT(strcmp(r.peer, "@user") == 0, "upload alias: peer must match");
     570            1 :     ASSERT(strcmp(r.out_path, "/tmp/file.txt") == 0, "upload alias: path must match");
     571              : }
     572              : 
     573              : /* ---- Test: upload with --caption ---- */
     574            1 : static void test_upload_with_caption(void) {
     575            1 :     char *argv[] = {"tg-cli", "upload", "@user", "/tmp/file.txt", "--caption", "My file", NULL};
     576              :     ArgResult r;
     577            1 :     int rc = arg_parse(6, argv, &r);
     578            1 :     ASSERT(rc == ARG_OK,                     "upload with caption: must return ARG_OK");
     579            1 :     ASSERT(r.command == CMD_SEND_FILE,       "upload with caption: CMD_SEND_FILE");
     580            1 :     ASSERT(strcmp(r.peer, "@user") == 0,     "upload with caption: peer must match");
     581            1 :     ASSERT(strcmp(r.out_path, "/tmp/file.txt") == 0, "upload with caption: path must match");
     582            1 :     ASSERT(strcmp(r.message, "My file") == 0, "upload with caption: caption must match");
     583              : }
     584              : 
     585              : /* ---- Test: dialogs --limit 0 (below range) → error ---- */
     586            1 : static void test_dialogs_limit_zero_is_error(void) {
     587            1 :     char *argv[] = {"tg-cli", "dialogs", "--limit", "0", NULL};
     588              :     ArgResult r;
     589            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     590              :            "dialogs --limit 0: must return ARG_ERROR (below range)");
     591              : }
     592              : 
     593              : /* ---- Test: dialogs --limit -5 (negative) → error ---- */
     594            1 : static void test_dialogs_limit_negative_is_error(void) {
     595            1 :     char *argv[] = {"tg-cli", "dialogs", "--limit", "-5", NULL};
     596              :     ArgResult r;
     597            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     598              :            "dialogs --limit -5: must return ARG_ERROR (negative)");
     599              : }
     600              : 
     601              : /* ---- Test: dialogs --limit 1001 (above range) → error ---- */
     602            1 : static void test_dialogs_limit_too_high(void) {
     603            1 :     char *argv[] = {"tg-cli", "dialogs", "--limit", "1001", NULL};
     604              :     ArgResult r;
     605            1 :     ASSERT(arg_parse(4, argv, &r) == ARG_ERROR,
     606              :            "dialogs --limit 1001: must return ARG_ERROR (above range)");
     607              : }
     608              : 
     609              : /* ---- Test: dialogs --limit boundary values (1 and 1000) ---- */
     610            1 : static void test_dialogs_limit_boundaries(void) {
     611              :     ArgResult r;
     612            1 :     char *argv_min[] = {"tg-cli", "dialogs", "--limit", "1", NULL};
     613            1 :     ASSERT(arg_parse(4, argv_min, &r) == ARG_OK,
     614              :            "dialogs --limit 1 (min): ARG_OK");
     615            1 :     ASSERT(r.limit == 1, "dialogs --limit 1: limit must be 1");
     616              : 
     617            1 :     char *argv_max[] = {"tg-cli", "dialogs", "--limit", "1000", NULL};
     618            1 :     ASSERT(arg_parse(4, argv_max, &r) == ARG_OK,
     619              :            "dialogs --limit 1000 (max): ARG_OK");
     620            1 :     ASSERT(r.limit == 1000, "dialogs --limit 1000: limit must be 1000");
     621              : }
     622              : 
     623              : /* ---- Test: history --limit 0 (below range) → error ---- */
     624            1 : static void test_history_limit_zero_is_error(void) {
     625            1 :     char *argv[] = {"tg-cli", "history", "@user", "--limit", "0", NULL};
     626              :     ArgResult r;
     627            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     628              :            "history --limit 0: must return ARG_ERROR (below range)");
     629              : }
     630              : 
     631              : /* ---- Test: history --limit -5 (negative) → error ---- */
     632            1 : static void test_history_limit_negative_is_error(void) {
     633            1 :     char *argv[] = {"tg-cli", "history", "@user", "--limit", "-5", NULL};
     634              :     ArgResult r;
     635            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     636              :            "history --limit -5: must return ARG_ERROR (negative)");
     637              : }
     638              : 
     639              : /* ---- Test: history --limit 1001 (above range) → error ---- */
     640            1 : static void test_history_limit_too_high(void) {
     641            1 :     char *argv[] = {"tg-cli", "history", "@user", "--limit", "1001", NULL};
     642              :     ArgResult r;
     643            1 :     ASSERT(arg_parse(5, argv, &r) == ARG_ERROR,
     644              :            "history --limit 1001: must return ARG_ERROR (above range)");
     645              : }
     646              : 
     647              : /* ---- Test: history --limit boundary values (1 and 1000) ---- */
     648            1 : static void test_history_limit_boundaries(void) {
     649              :     ArgResult r;
     650            1 :     char *argv_min[] = {"tg-cli", "history", "@user", "--limit", "1", NULL};
     651            1 :     ASSERT(arg_parse(5, argv_min, &r) == ARG_OK,
     652              :            "history --limit 1 (min): ARG_OK");
     653            1 :     ASSERT(r.limit == 1, "history --limit 1: limit must be 1");
     654              : 
     655            1 :     char *argv_max[] = {"tg-cli", "history", "@user", "--limit", "1000", NULL};
     656            1 :     ASSERT(arg_parse(5, argv_max, &r) == ARG_OK,
     657              :            "history --limit 1000 (max): ARG_OK");
     658            1 :     ASSERT(r.limit == 1000, "history --limit 1000: limit must be 1000");
     659              : }
     660              : 
     661            1 : void run_arg_parse_tests(void) {
     662            1 :     RUN_TEST(test_no_args);
     663            1 :     RUN_TEST(test_help_flag);
     664            1 :     RUN_TEST(test_version_flag);
     665            1 :     RUN_TEST(test_global_flags);
     666            1 :     RUN_TEST(test_config_flag);
     667            1 :     RUN_TEST(test_config_flag_missing_value);
     668            1 :     RUN_TEST(test_dialogs_no_options);
     669            1 :     RUN_TEST(test_dialogs_with_limit);
     670            1 :     RUN_TEST(test_dialogs_limit_missing);
     671            1 :     RUN_TEST(test_dialogs_limit_zero_is_error);
     672            1 :     RUN_TEST(test_dialogs_limit_negative_is_error);
     673            1 :     RUN_TEST(test_dialogs_limit_too_high);
     674            1 :     RUN_TEST(test_dialogs_limit_boundaries);
     675            1 :     RUN_TEST(test_history_basic);
     676            1 :     RUN_TEST(test_history_with_options);
     677            1 :     RUN_TEST(test_history_missing_peer);
     678            1 :     RUN_TEST(test_history_limit_zero_is_error);
     679            1 :     RUN_TEST(test_history_limit_negative_is_error);
     680            1 :     RUN_TEST(test_history_limit_too_high);
     681            1 :     RUN_TEST(test_history_limit_boundaries);
     682            1 :     RUN_TEST(test_send_basic);
     683            1 :     RUN_TEST(test_send_missing_peer);
     684            1 :     RUN_TEST(test_send_missing_message);
     685            1 :     RUN_TEST(test_search_query_only);
     686            1 :     RUN_TEST(test_search_peer_and_query);
     687            1 :     RUN_TEST(test_search_missing_query);
     688            1 :     RUN_TEST(test_contacts);
     689            1 :     RUN_TEST(test_user_info);
     690            1 :     RUN_TEST(test_user_info_missing_peer);
     691            1 :     RUN_TEST(test_unknown_subcommand);
     692            1 :     RUN_TEST(test_null_args);
     693            1 :     RUN_TEST(test_json_before_subcommand);
     694            1 :     RUN_TEST(test_dialogs_limit_non_numeric);
     695            1 :     RUN_TEST(test_dialogs_unknown_option);
     696            1 :     RUN_TEST(test_history_dash_peer);
     697            1 :     RUN_TEST(test_history_limit_missing);
     698            1 :     RUN_TEST(test_history_limit_non_numeric);
     699            1 :     RUN_TEST(test_history_offset_missing);
     700            1 :     RUN_TEST(test_history_offset_non_numeric);
     701            1 :     RUN_TEST(test_history_unknown_option);
     702            1 :     RUN_TEST(test_send_dash_peer);
     703            1 :     RUN_TEST(test_search_all_dash);
     704            1 :     RUN_TEST(test_batch_no_subcommand);
     705            1 :     RUN_TEST(test_dialogs_archived);
     706            1 :     RUN_TEST(test_dialogs_archived_with_limit);
     707            1 :     RUN_TEST(test_dialogs_not_archived_by_default);
     708            1 :     RUN_TEST(test_watch_interval_valid);
     709            1 :     RUN_TEST(test_watch_interval_boundaries);
     710            1 :     RUN_TEST(test_watch_interval_too_low);
     711            1 :     RUN_TEST(test_watch_interval_too_high);
     712            1 :     RUN_TEST(test_watch_interval_non_numeric);
     713            1 :     RUN_TEST(test_watch_interval_missing_value);
     714            1 :     RUN_TEST(test_watch_default_interval);
     715            1 :     RUN_TEST(test_history_no_media_flag);
     716            1 :     RUN_TEST(test_history_no_media_default_zero);
     717            1 :     RUN_TEST(test_search_default_limit);
     718            1 :     RUN_TEST(test_search_with_limit);
     719            1 :     RUN_TEST(test_search_limit_too_low);
     720            1 :     RUN_TEST(test_search_limit_too_high);
     721            1 :     RUN_TEST(test_search_limit_boundaries);
     722            1 :     RUN_TEST(test_search_peer_query_limit);
     723            1 :     RUN_TEST(test_upload_alias);
     724            1 :     RUN_TEST(test_upload_with_caption);
     725            1 :     RUN_TEST(test_watch_peers_single);
     726            1 :     RUN_TEST(test_watch_peers_multi);
     727            1 :     RUN_TEST(test_watch_no_peers);
     728            1 :     RUN_TEST(test_watch_peers_missing_value);
     729            1 : }
        

Generated by: LCOV version 2.0-1