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