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

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file test_config_wizard_batch.c
       6              :  * @brief Functional tests for config_wizard_run_batch() (FEAT-37).
       7              :  *
       8              :  * Tests:
       9              :  *   - Happy path: writes config.ini with mode 0600 and correct content.
      10              :  *   - Refuses to overwrite an existing non-empty config without --force.
      11              :  *   - Accepts overwrite with --force.
      12              :  *   - Rejects malformed api_id (0, negative, non-numeric).
      13              :  *   - Rejects malformed api_hash (wrong length, uppercase, non-hex).
      14              :  */
      15              : 
      16              : #include "test_helpers.h"
      17              : #include "app/config_wizard.h"
      18              : 
      19              : #include <stdio.h>
      20              : #include <stdlib.h>
      21              : #include <string.h>
      22              : #include <sys/stat.h>
      23              : #include <unistd.h>
      24              : 
      25              : /* ---- Helpers ---- */
      26              : 
      27              : /** Return a temp dir unique to this test run. */
      28           60 : static const char *get_tmp_dir(void) {
      29              :     static char tmp[256];
      30           60 :     if (tmp[0]) return tmp;
      31            2 :     const char *t = getenv("TMPDIR");
      32            2 :     if (!t) t = "/tmp";
      33            2 :     snprintf(tmp, sizeof(tmp), "%s/tg-cli-wiz-test-%d", t, (int)getpid());
      34            2 :     return tmp;
      35              : }
      36              : 
      37              : /** Set XDG_CONFIG_HOME to @p dir so config_wizard writes there. */
      38           22 : static void set_config_home(const char *dir) {
      39           22 :     setenv("XDG_CONFIG_HOME", dir, 1);
      40           22 : }
      41              : 
      42              : /** Build the expected config.ini path. */
      43           32 : static void cfg_path(char *out, size_t cap) {
      44           32 :     snprintf(out, cap, "%s/tg-cli/config.ini", get_tmp_dir());
      45           32 : }
      46              : 
      47              : /** Remove the test config file (ignore errors). */
      48           24 : static void rm_cfg(void) {
      49           24 :     char p[512]; cfg_path(p, sizeof(p));
      50           24 :     unlink(p);
      51           24 : }
      52              : 
      53              : /** Create a non-empty config file to simulate "already configured". */
      54            4 : static void create_nonempty_cfg(void) {
      55            4 :     char p[512]; cfg_path(p, sizeof(p));
      56            4 :     char dir[512]; snprintf(dir, sizeof(dir), "%s/tg-cli", get_tmp_dir());
      57              :     /* mkdir -p */
      58            4 :     char cmd[1024]; snprintf(cmd, sizeof(cmd), "mkdir -p %s", dir);
      59            4 :     int sysrc = system(cmd); (void)sysrc;
      60            4 :     FILE *fp = fopen(p, "w");
      61            4 :     if (fp) { fprintf(fp, "api_id=99999\napi_hash=%s\n", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"); fclose(fp); }
      62            4 : }
      63              : 
      64              : /* ---- Tests ---- */
      65              : 
      66            2 : static void test_batch_happy_path(void) {
      67            2 :     set_config_home(get_tmp_dir());
      68            2 :     rm_cfg();
      69              : 
      70            2 :     int rc = config_wizard_run_batch("12345",
      71              :                                      "deadbeefdeadbeefdeadbeefdeadbeef",
      72              :                                      0);
      73            2 :     ASSERT(rc == 0, "batch happy: must return 0");
      74              : 
      75              :     /* Check file exists. */
      76            2 :     char p[512]; cfg_path(p, sizeof(p));
      77              :     struct stat st;
      78            2 :     ASSERT(stat(p, &st) == 0, "batch happy: config.ini must exist");
      79              : 
      80              :     /* Check mode 0600. */
      81            2 :     ASSERT((st.st_mode & 0777) == 0600, "batch happy: mode must be 0600");
      82              : 
      83              :     /* Check content. */
      84            2 :     FILE *fp = fopen(p, "r");
      85            2 :     ASSERT(fp != NULL, "batch happy: can open config.ini");
      86            2 :     char content[512] = {0};
      87            2 :     size_t n = fread(content, 1, sizeof(content) - 1, fp);
      88            2 :     fclose(fp);
      89            2 :     ASSERT(n > 0, "batch happy: config.ini is non-empty");
      90            2 :     ASSERT(strstr(content, "api_id=12345") != NULL,
      91              :            "batch happy: config.ini contains api_id=12345");
      92            2 :     ASSERT(strstr(content, "api_hash=deadbeefdeadbeefdeadbeefdeadbeef") != NULL,
      93              :            "batch happy: config.ini contains correct api_hash");
      94              : 
      95            2 :     rm_cfg();
      96              : }
      97              : 
      98            2 : static void test_batch_refuses_overwrite_without_force(void) {
      99            2 :     set_config_home(get_tmp_dir());
     100            2 :     create_nonempty_cfg();
     101              : 
     102            2 :     int rc = config_wizard_run_batch("12345",
     103              :                                      "deadbeefdeadbeefdeadbeefdeadbeef",
     104              :                                      0 /* force=0 */);
     105            2 :     ASSERT(rc != 0, "batch no-force: must refuse to overwrite existing config");
     106              : 
     107            2 :     rm_cfg();
     108              : }
     109              : 
     110            2 : static void test_batch_force_overwrites(void) {
     111            2 :     set_config_home(get_tmp_dir());
     112            2 :     create_nonempty_cfg();
     113              : 
     114            2 :     int rc = config_wizard_run_batch("99",
     115              :                                      "abcdef0123456789abcdef0123456789",
     116              :                                      1 /* force=1 */);
     117            2 :     ASSERT(rc == 0, "batch --force: must succeed");
     118              : 
     119            2 :     char p[512]; cfg_path(p, sizeof(p));
     120            2 :     FILE *fp = fopen(p, "r");
     121            2 :     ASSERT(fp != NULL, "batch --force: config.ini must exist after overwrite");
     122            2 :     char content[512] = {0};
     123            2 :     size_t nf = fread(content, 1, sizeof(content) - 1, fp);
     124            2 :     fclose(fp);
     125              :     (void)nf;
     126            2 :     ASSERT(strstr(content, "api_id=99") != NULL,
     127              :            "batch --force: config.ini has new api_id");
     128              : 
     129            2 :     rm_cfg();
     130              : }
     131              : 
     132            2 : static void test_batch_rejects_zero_api_id(void) {
     133            2 :     set_config_home(get_tmp_dir());
     134            2 :     rm_cfg();
     135            2 :     ASSERT(config_wizard_run_batch("0", "deadbeefdeadbeefdeadbeefdeadbeef", 0) != 0,
     136              :            "api_id=0: must fail");
     137              : }
     138              : 
     139            2 : static void test_batch_rejects_negative_api_id(void) {
     140            2 :     set_config_home(get_tmp_dir());
     141            2 :     rm_cfg();
     142            2 :     ASSERT(config_wizard_run_batch("-1", "deadbeefdeadbeefdeadbeefdeadbeef", 0) != 0,
     143              :            "api_id=-1: must fail");
     144              : }
     145              : 
     146            2 : static void test_batch_rejects_non_numeric_api_id(void) {
     147            2 :     set_config_home(get_tmp_dir());
     148            2 :     rm_cfg();
     149            2 :     ASSERT(config_wizard_run_batch("abc", "deadbeefdeadbeefdeadbeefdeadbeef", 0) != 0,
     150              :            "api_id=abc: must fail");
     151              : }
     152              : 
     153            2 : static void test_batch_rejects_wrong_length_api_hash(void) {
     154            2 :     set_config_home(get_tmp_dir());
     155            2 :     rm_cfg();
     156              :     /* 31 chars — too short */
     157            2 :     ASSERT(config_wizard_run_batch("12345", "deadbeefdeadbeefdeadbeefdeadbee", 0) != 0,
     158              :            "api_hash 31 chars: must fail");
     159              : }
     160              : 
     161            2 : static void test_batch_rejects_uppercase_api_hash(void) {
     162            2 :     set_config_home(get_tmp_dir());
     163            2 :     rm_cfg();
     164              :     /* 32 chars but uppercase */
     165            2 :     ASSERT(config_wizard_run_batch("12345", "DEADBEEFDEADBEEFDEADBEEFDEADBEEF", 0) != 0,
     166              :            "api_hash uppercase: must fail");
     167              : }
     168              : 
     169            2 : static void test_batch_rejects_non_hex_api_hash(void) {
     170            2 :     set_config_home(get_tmp_dir());
     171            2 :     rm_cfg();
     172              :     /* 32 chars but 'z' is not hex */
     173            2 :     ASSERT(config_wizard_run_batch("12345", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", 0) != 0,
     174              :            "api_hash non-hex: must fail");
     175              : }
     176              : 
     177            2 : static void test_batch_null_args(void) {
     178            2 :     set_config_home(get_tmp_dir());
     179            2 :     rm_cfg();
     180            2 :     ASSERT(config_wizard_run_batch(NULL, "deadbeefdeadbeefdeadbeefdeadbeef", 0) != 0,
     181              :            "null api_id_str: must fail");
     182            2 :     ASSERT(config_wizard_run_batch("12345", NULL, 0) != 0,
     183              :            "null api_hash_str: must fail");
     184              : }
     185              : 
     186              : /* Script-invocation guard: when stdin is not a TTY (e.g. piped from a
     187              :  * shell script or a cron job), config_wizard_run_interactive() must
     188              :  * bail out immediately with a clear error rather than block on fgets
     189              :  * (which would read EOF in a tight loop or hang). CTest runs its
     190              :  * children with stdin attached to a pipe, so calling the function
     191              :  * here is the exact script scenario. */
     192            2 : static void test_interactive_refuses_when_stdin_not_tty(void) {
     193            2 :     set_config_home(get_tmp_dir());
     194            2 :     rm_cfg();
     195            2 :     ASSERT(!isatty(STDIN_FILENO),
     196              :            "precondition: CTest gives us a non-TTY stdin");
     197              :     /* Must return -1 quickly — no read, no hang. */
     198            2 :     ASSERT(config_wizard_run_interactive() == -1,
     199              :            "interactive wizard: must refuse non-TTY stdin with rc=-1");
     200              :     /* Must NOT have created the config file on this error path. */
     201              :     struct stat st;
     202              :     char cfg[1024];
     203            2 :     snprintf(cfg, sizeof(cfg), "%s/tg-cli/config.ini", get_tmp_dir());
     204            2 :     ASSERT(stat(cfg, &st) != 0,
     205              :            "interactive wizard: must not create config on TTY guard failure");
     206              : }
     207              : 
     208              : /* ---- Runner ---- */
     209              : 
     210            2 : void run_config_wizard_batch_tests(void) {
     211            2 :     RUN_TEST(test_batch_happy_path);
     212            2 :     RUN_TEST(test_batch_refuses_overwrite_without_force);
     213            2 :     RUN_TEST(test_batch_force_overwrites);
     214            2 :     RUN_TEST(test_batch_rejects_zero_api_id);
     215            2 :     RUN_TEST(test_batch_rejects_negative_api_id);
     216            2 :     RUN_TEST(test_batch_rejects_non_numeric_api_id);
     217            2 :     RUN_TEST(test_batch_rejects_wrong_length_api_hash);
     218            2 :     RUN_TEST(test_batch_rejects_uppercase_api_hash);
     219            2 :     RUN_TEST(test_batch_rejects_non_hex_api_hash);
     220            2 :     RUN_TEST(test_batch_null_args);
     221            2 :     RUN_TEST(test_interactive_refuses_when_stdin_not_tty);
     222            2 : }
        

Generated by: LCOV version 2.0-1