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

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "config_store.h"
       3              : #include "fs_util.h"
       4              : #include "logger.h"
       5              : #include "raii.h"
       6              : #include <string.h>
       7              : #include <stdlib.h>
       8              : #include <unistd.h>
       9              : #include <sys/stat.h>
      10              : 
      11           10 : static void config_cleanup(void *ptr) {
      12           10 :     Config **cfg = (Config **)ptr;
      13           10 :     if (cfg && *cfg) {
      14            5 :         config_free(*cfg);
      15            5 :         *cfg = NULL;
      16              :     }
      17           10 : }
      18              : 
      19            1 : void test_config_store(void) {
      20            1 :     char *old_home = getenv("HOME");
      21            1 :     char *old_xdg  = getenv("XDG_CONFIG_HOME");
      22            1 :     setenv("HOME", "/tmp/email-cli-test-home", 1);
      23            1 :     unsetenv("XDG_CONFIG_HOME");
      24              :     /* Pre-clean: remove any leftover account from previous test runs */
      25            1 :     unlink("/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini");
      26            1 :     rmdir("/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com");
      27            1 :     rmdir("/tmp/email-cli-test-home/.config/email-cli/accounts");
      28              : 
      29              :     // 1. Test Save
      30              :     {
      31            1 :         Config cfg = {0};
      32            1 :         cfg.host   = strdup("imaps://imap.test.com");
      33            1 :         cfg.user   = strdup("test@test.com");
      34            1 :         cfg.pass   = strdup("password123");
      35            1 :         cfg.folder = strdup("INBOX");
      36              : 
      37            1 :         int res = config_save_to_store(&cfg);
      38            1 :         ASSERT(res == 0, "config_save_to_store should return 0");
      39              : 
      40            1 :         free(cfg.host);
      41            1 :         free(cfg.user);
      42            1 :         free(cfg.pass);
      43            1 :         free(cfg.folder);
      44              :     }
      45              : 
      46              :     // 2. Test Load - full config
      47              :     {
      48            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
      49            1 :         ASSERT(loaded != NULL, "config_load_from_store should not return NULL");
      50            1 :         ASSERT(strcmp(loaded->host, "imaps://imap.test.com") == 0, "Host should match");
      51            1 :         ASSERT(strcmp(loaded->user, "test@test.com") == 0, "User should match");
      52            1 :         ASSERT(strcmp(loaded->pass, "password123") == 0, "Pass should match");
      53              :     }
      54              : 
      55              :     // 3. Test Load - incomplete config (missing host) → should return NULL
      56              :     {
      57            1 :         const char *acct_path =
      58              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
      59            1 :         FILE *fp = fopen(acct_path, "w");
      60            1 :         if (fp) {
      61            1 :             fprintf(fp, "EMAIL_USER=test@test.com\n");
      62            1 :             fprintf(fp, "EMAIL_PASS=password123\n");
      63            1 :             fclose(fp);
      64              :         }
      65            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
      66            1 :         ASSERT(loaded == NULL, "config_load_from_store should return NULL for incomplete config");
      67              :     }
      68              : 
      69              :     // 4. Test Load - no config file → should return NULL
      70              :     {
      71            1 :         unlink("/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini");
      72            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
      73            1 :         ASSERT(loaded == NULL, "config_load_from_store should return NULL when file is missing");
      74              :     }
      75              : 
      76              :     // 5. Test Save/Load with ssl_no_verify=1
      77              :     {
      78            1 :         Config cfg2 = {0};
      79            1 :         cfg2.host        = strdup("imaps://imap.test.com");
      80            1 :         cfg2.user        = strdup("test@test.com");
      81            1 :         cfg2.pass        = strdup("password123");
      82            1 :         cfg2.folder      = strdup("INBOX");
      83            1 :         cfg2.ssl_no_verify = 1;
      84              : 
      85            1 :         int res = config_save_to_store(&cfg2);
      86            1 :         ASSERT(res == 0, "config_save_to_store with ssl_no_verify=1 should return 0");
      87              : 
      88            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded2 = config_load_from_store();
      89            1 :         ASSERT(loaded2 != NULL, "config_load_from_store should not return NULL");
      90            1 :         ASSERT(loaded2->ssl_no_verify == 1, "ssl_no_verify should be 1 after load");
      91              : 
      92            1 :         free(cfg2.host);
      93            1 :         free(cfg2.user);
      94            1 :         free(cfg2.pass);
      95            1 :         free(cfg2.folder);
      96              :     }
      97              : 
      98              :     // 6. Test Load - host without protocol prefix → should return NULL with error
      99              :     {
     100            1 :         const char *acct_path =
     101              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
     102            1 :         FILE *fp = fopen(acct_path, "w");
     103            1 :         if (fp) {
     104            1 :             fprintf(fp, "EMAIL_HOST=box.example.com\n");
     105            1 :             fprintf(fp, "EMAIL_USER=test@test.com\n");
     106            1 :             fprintf(fp, "EMAIL_PASS=password123\n");
     107            1 :             fclose(fp);
     108              :         }
     109            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
     110            1 :         ASSERT(loaded == NULL, "config_load_from_store should return NULL for host without protocol");
     111            1 :         unlink(acct_path);
     112              :     }
     113              : 
     114              :     // 7. Test config_free with NULL (should not crash)
     115            1 :     config_free(NULL);
     116              : 
     117              :     // 8. config_save_to_store fails: fs_mkdir_p fails
     118              :     // Make .config directory non-writable so subdirectory creation fails
     119              :     {
     120            1 :         setenv("HOME", "/tmp/email-cli-test-home-fail", 1);
     121            1 :         fs_mkdir_p("/tmp/email-cli-test-home-fail/.config", 0700);
     122            1 :         chmod("/tmp/email-cli-test-home-fail/.config", 0000);
     123              : 
     124            1 :         Config cfg_fail = {0};
     125            1 :         cfg_fail.host   = strdup("imaps://imap.test.com");
     126            1 :         cfg_fail.user   = strdup("test@test.com");
     127            1 :         cfg_fail.pass   = strdup("password123");
     128            1 :         cfg_fail.folder = strdup("INBOX");
     129              : 
     130            1 :         int res = config_save_to_store(&cfg_fail);
     131            1 :         ASSERT(res == -1, "config_save_to_store: mkdir fail should return -1");
     132              : 
     133            1 :         chmod("/tmp/email-cli-test-home-fail/.config", 0700);
     134            1 :         free(cfg_fail.host);
     135            1 :         free(cfg_fail.user);
     136            1 :         free(cfg_fail.pass);
     137            1 :         free(cfg_fail.folder);
     138              :     }
     139              : 
     140              :     // 9. config_save_to_store fails: fopen fails
     141              :     // Create account dir, then place a directory at the config.ini path
     142              :     {
     143            1 :         setenv("HOME", "/tmp/email-cli-test-home-fopen", 1);
     144            1 :         fs_mkdir_p("/tmp/email-cli-test-home-fopen/.config/email-cli/accounts/test@test.com",
     145              :                    0700);
     146              :         // Create a directory at the config file path to make fopen("w") fail
     147            1 :         mkdir("/tmp/email-cli-test-home-fopen/.config/email-cli/accounts/test@test.com/config.ini",
     148              :               0700);
     149              : 
     150            1 :         Config cfg_fopen = {0};
     151            1 :         cfg_fopen.host   = strdup("imaps://imap.test.com");
     152            1 :         cfg_fopen.user   = strdup("test@test.com");
     153            1 :         cfg_fopen.pass   = strdup("password123");
     154            1 :         cfg_fopen.folder = strdup("INBOX");
     155              : 
     156            1 :         int res = config_save_to_store(&cfg_fopen);
     157            1 :         ASSERT(res == -1, "config_save_to_store: fopen on dir should return -1");
     158              : 
     159            1 :         rmdir("/tmp/email-cli-test-home-fopen/.config/email-cli/accounts/test@test.com/config.ini");
     160            1 :         free(cfg_fopen.host);
     161            1 :         free(cfg_fopen.user);
     162            1 :         free(cfg_fopen.pass);
     163            1 :         free(cfg_fopen.folder);
     164              :     }
     165              : 
     166              :     // 10. SMTP fields round-trip (covers lines 79-82 in config_store.c)
     167              :     {
     168            1 :         setenv("HOME", "/tmp/email-cli-test-home", 1);
     169            1 :         const char *acct_path =
     170              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
     171            1 :         FILE *fp = fopen(acct_path, "w");
     172            1 :         if (fp) {
     173            1 :             fprintf(fp, "EMAIL_HOST=imaps://imap.test.com\n");
     174            1 :             fprintf(fp, "EMAIL_USER=test@test.com\n");
     175            1 :             fprintf(fp, "EMAIL_PASS=password123\n");
     176            1 :             fprintf(fp, "SMTP_HOST=smtps://smtp.test.com\n");
     177            1 :             fprintf(fp, "SMTP_PORT=465\n");
     178            1 :             fprintf(fp, "SMTP_USER=test@test.com\n");
     179            1 :             fprintf(fp, "SMTP_PASS=smtppass\n");
     180            1 :             fclose(fp);
     181              :         }
     182            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
     183            1 :         ASSERT(loaded != NULL, "smtp fields: should load");
     184            1 :         if (loaded) {
     185            1 :             ASSERT(loaded->smtp_host && strcmp(loaded->smtp_host, "smtps://smtp.test.com") == 0,
     186              :                    "smtp fields: smtp_host matches");
     187            1 :             ASSERT(loaded->smtp_port == 465, "smtp fields: smtp_port matches");
     188            1 :             ASSERT(loaded->smtp_user && strcmp(loaded->smtp_user, "test@test.com") == 0,
     189              :                    "smtp fields: smtp_user matches");
     190            1 :             ASSERT(loaded->smtp_pass && strcmp(loaded->smtp_pass, "smtppass") == 0,
     191              :                    "smtp fields: smtp_pass matches");
     192              :         }
     193            1 :         unlink(acct_path);
     194              :     }
     195              : 
     196              :     // 11. Gmail mode config load (covers lines 83-86 in config_store.c)
     197              :     {
     198            1 :         setenv("HOME", "/tmp/email-cli-test-home", 1);
     199            1 :         const char *acct_path =
     200              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
     201            1 :         FILE *fp = fopen(acct_path, "w");
     202            1 :         if (fp) {
     203            1 :             fprintf(fp, "EMAIL_USER=gmailuser@gmail.com\n");
     204            1 :             fprintf(fp, "GMAIL_MODE=1\n");
     205            1 :             fprintf(fp, "GMAIL_REFRESH_TOKEN=myrefreshtoken\n");
     206            1 :             fprintf(fp, "GMAIL_CLIENT_ID=myclientid\n");
     207            1 :             fprintf(fp, "GMAIL_CLIENT_SECRET=myclientsecret\n");
     208            1 :             fclose(fp);
     209              :         }
     210            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
     211            1 :         ASSERT(loaded != NULL, "gmail mode: should load");
     212            1 :         if (loaded) {
     213            1 :             ASSERT(loaded->gmail_mode == 1, "gmail mode: gmail_mode=1");
     214            1 :             ASSERT(loaded->gmail_refresh_token &&
     215              :                    strcmp(loaded->gmail_refresh_token, "myrefreshtoken") == 0,
     216              :                    "gmail mode: refresh token matches");
     217            1 :             ASSERT(loaded->gmail_client_id &&
     218              :                    strcmp(loaded->gmail_client_id, "myclientid") == 0,
     219              :                    "gmail mode: client_id matches");
     220            1 :             ASSERT(loaded->gmail_client_secret &&
     221              :                    strcmp(loaded->gmail_client_secret, "myclientsecret") == 0,
     222              :                    "gmail mode: client_secret matches");
     223              :         }
     224            1 :         unlink(acct_path);
     225              :     }
     226              : 
     227              :     // 12. Gmail mode: missing refresh token → NULL (covers line 92)
     228              :     {
     229            1 :         setenv("HOME", "/tmp/email-cli-test-home", 1);
     230            1 :         const char *acct_path =
     231              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
     232            1 :         FILE *fp = fopen(acct_path, "w");
     233            1 :         if (fp) {
     234            1 :             fprintf(fp, "EMAIL_USER=gmailuser@gmail.com\n");
     235            1 :             fprintf(fp, "GMAIL_MODE=1\n");
     236              :             /* no GMAIL_REFRESH_TOKEN */
     237            1 :             fclose(fp);
     238              :         }
     239            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
     240            1 :         ASSERT(loaded == NULL,
     241              :                "gmail missing token: config_load should return NULL");
     242            1 :         unlink(acct_path);
     243              :     }
     244              : 
     245              :     // 13. SMTP host without smtps:// prefix → rejected (covers lines 110-118)
     246              :     {
     247            1 :         setenv("HOME", "/tmp/email-cli-test-home", 1);
     248            1 :         const char *acct_path =
     249              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
     250            1 :         FILE *fp = fopen(acct_path, "w");
     251            1 :         if (fp) {
     252            1 :             fprintf(fp, "EMAIL_HOST=imaps://imap.test.com\n");
     253            1 :             fprintf(fp, "EMAIL_USER=test@test.com\n");
     254            1 :             fprintf(fp, "EMAIL_PASS=password123\n");
     255            1 :             fprintf(fp, "SMTP_HOST=smtp.test.com\n");  /* missing smtps:// */
     256            1 :             fclose(fp);
     257              :         }
     258            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
     259            1 :         ASSERT(loaded == NULL,
     260              :                "insecure smtp host: config_load should return NULL");
     261            1 :         unlink(acct_path);
     262              :     }
     263              : 
     264              :     // 14. ssl_no_verify=1 with non-TLS host and SMTP → warnings logged
     265              :     //     (covers lines 124 and 128-129)
     266              :     {
     267            1 :         setenv("HOME", "/tmp/email-cli-test-home", 1);
     268            1 :         const char *acct_path =
     269              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/test@test.com/config.ini";
     270            1 :         FILE *fp = fopen(acct_path, "w");
     271            1 :         if (fp) {
     272            1 :             fprintf(fp, "EMAIL_HOST=imap.insecure.com\n");
     273            1 :             fprintf(fp, "EMAIL_USER=test@test.com\n");
     274            1 :             fprintf(fp, "EMAIL_PASS=password123\n");
     275            1 :             fprintf(fp, "SSL_NO_VERIFY=1\n");
     276            1 :             fprintf(fp, "SMTP_HOST=smtp.insecure.com\n");
     277            1 :             fclose(fp);
     278              :         }
     279            2 :         RAII_WITH_CLEANUP(config_cleanup) Config *loaded = config_load_from_store();
     280            1 :         ASSERT(loaded != NULL,
     281              :                "ssl_no_verify non-tls: should still load (warnings only)");
     282            1 :         if (loaded) {
     283            1 :             ASSERT(loaded->ssl_no_verify == 1, "ssl_no_verify non-tls: flag set");
     284              :         }
     285            1 :         unlink(acct_path);
     286              :     }
     287              : 
     288              :     // 15. config_delete_account: non-empty dir → rmdir fails, returns -1
     289              :     //     (covers lines 194-195)
     290              :     {
     291            1 :         setenv("HOME", "/tmp/email-cli-test-home", 1);
     292            1 :         const char *dir =
     293              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/nodelete@test.com";
     294            1 :         const char *extra_file =
     295              :             "/tmp/email-cli-test-home/.config/email-cli/accounts/nodelete@test.com/extra.txt";
     296            1 :         fs_mkdir_p(dir, 0700);
     297            1 :         FILE *fp = fopen(extra_file, "w");
     298            1 :         if (fp) { fprintf(fp, "extra\n"); fclose(fp); }
     299              : 
     300            1 :         int rc = config_delete_account("nodelete@test.com");
     301            1 :         ASSERT(rc == -1, "delete non-empty dir: should return -1");
     302              : 
     303              :         /* cleanup */
     304            1 :         unlink(extra_file);
     305            1 :         rmdir(dir);
     306              :     }
     307              : 
     308              :     // 16. Multiple accounts → sorting path exercised (covers lines 243-264)
     309              :     {
     310            1 :         setenv("HOME", "/tmp/email-cli-test-multiacct", 1);
     311            1 :         unsetenv("XDG_CONFIG_HOME");
     312            1 :         const char *base =
     313              :             "/tmp/email-cli-test-multiacct/.config/email-cli/accounts";
     314              : 
     315              :         /* Account 1: bob@beta.com — different domain */
     316              :         char path[256];
     317            1 :         snprintf(path, sizeof(path), "%s/bob@beta.com", base);
     318            1 :         fs_mkdir_p(path, 0700);
     319            1 :         snprintf(path, sizeof(path), "%s/bob@beta.com/config.ini", base);
     320              :         {
     321            1 :             FILE *fp = fopen(path, "w");
     322            1 :             if (fp) {
     323            1 :                 fprintf(fp, "EMAIL_HOST=imaps://imap.beta.com\n");
     324            1 :                 fprintf(fp, "EMAIL_USER=bob@beta.com\n");
     325            1 :                 fprintf(fp, "EMAIL_PASS=pass1\n");
     326            1 :                 fclose(fp);
     327              :             }
     328              :         }
     329              :         /* Account 2: alice@alpha.com — different domain */
     330            1 :         snprintf(path, sizeof(path), "%s/alice@alpha.com", base);
     331            1 :         fs_mkdir_p(path, 0700);
     332            1 :         snprintf(path, sizeof(path), "%s/alice@alpha.com/config.ini", base);
     333              :         {
     334            1 :             FILE *fp = fopen(path, "w");
     335            1 :             if (fp) {
     336            1 :                 fprintf(fp, "EMAIL_HOST=imaps://imap.alpha.com\n");
     337            1 :                 fprintf(fp, "EMAIL_USER=alice@alpha.com\n");
     338            1 :                 fprintf(fp, "EMAIL_PASS=pass2\n");
     339            1 :                 fclose(fp);
     340              :             }
     341              :         }
     342              :         /* Account 3: zara@alpha.com — same domain as alice → hits same-domain
     343              :          * sort branch (lines 256-259). zara > alice alphabetically, so alice
     344              :          * should sort before zara. */
     345            1 :         snprintf(path, sizeof(path), "%s/zara@alpha.com", base);
     346            1 :         fs_mkdir_p(path, 0700);
     347            1 :         snprintf(path, sizeof(path), "%s/zara@alpha.com/config.ini", base);
     348              :         {
     349            1 :             FILE *fp = fopen(path, "w");
     350            1 :             if (fp) {
     351            1 :                 fprintf(fp, "EMAIL_HOST=imaps://imap.alpha.com\n");
     352            1 :                 fprintf(fp, "EMAIL_USER=zara@alpha.com\n");
     353            1 :                 fprintf(fp, "EMAIL_PASS=pass3\n");
     354            1 :                 fclose(fp);
     355              :             }
     356              :         }
     357              : 
     358            1 :         int count = 0;
     359            1 :         AccountEntry *list = config_list_accounts(&count);
     360            1 :         ASSERT(count == 3, "multi-acct sort: count=3");
     361            1 :         if (list && count >= 3) {
     362              :             /* After sorting: alpha.com accounts first (alice, zara), then beta.com */
     363            1 :             ASSERT(strstr(list[0].name, "alpha.com") != NULL,
     364              :                    "multi-acct sort: first account is alpha.com");
     365            1 :             ASSERT(strstr(list[1].name, "alpha.com") != NULL,
     366              :                    "multi-acct sort: second account is alpha.com");
     367              :             /* Within alpha.com: alice < zara */
     368            1 :             ASSERT(strncmp(list[0].name, "alice", 5) == 0,
     369              :                    "multi-acct sort: alice before zara");
     370            1 :             ASSERT(strstr(list[2].name, "beta.com") != NULL,
     371              :                    "multi-acct sort: beta.com last");
     372              :         }
     373            1 :         config_free_account_list(list, count);
     374              : 
     375              :         /* cleanup */
     376            1 :         unlink("/tmp/email-cli-test-multiacct/.config/email-cli/accounts/bob@beta.com/config.ini");
     377            1 :         rmdir("/tmp/email-cli-test-multiacct/.config/email-cli/accounts/bob@beta.com");
     378            1 :         unlink("/tmp/email-cli-test-multiacct/.config/email-cli/accounts/alice@alpha.com/config.ini");
     379            1 :         rmdir("/tmp/email-cli-test-multiacct/.config/email-cli/accounts/alice@alpha.com");
     380            1 :         unlink("/tmp/email-cli-test-multiacct/.config/email-cli/accounts/zara@alpha.com/config.ini");
     381            1 :         rmdir("/tmp/email-cli-test-multiacct/.config/email-cli/accounts/zara@alpha.com");
     382              :     }
     383              : 
     384              :     // 17. config_list_accounts realloc path: create 9 accounts (cap starts at
     385              :     //     8, so the 9th triggers realloc) — covers lines 227-230
     386              :     {
     387            1 :         setenv("HOME", "/tmp/email-cli-test-manyacct", 1);
     388            1 :         unsetenv("XDG_CONFIG_HOME");
     389            1 :         const char *base =
     390              :             "/tmp/email-cli-test-manyacct/.config/email-cli/accounts";
     391              : 
     392              :         /* usernames chosen to produce a valid IMAP host pattern */
     393            1 :         const char *users[] = {
     394              :             "u1@svc.io", "u2@svc.io", "u3@svc.io", "u4@svc.io",
     395              :             "u5@svc.io", "u6@svc.io", "u7@svc.io", "u8@svc.io",
     396              :             "u9@svc.io"
     397              :         };
     398            1 :         int nacct = (int)(sizeof(users) / sizeof(users[0]));
     399              :         char path[256];
     400           10 :         for (int i = 0; i < nacct; i++) {
     401            9 :             snprintf(path, sizeof(path), "%s/%s", base, users[i]);
     402            9 :             fs_mkdir_p(path, 0700);
     403            9 :             snprintf(path, sizeof(path), "%s/%s/config.ini", base, users[i]);
     404            9 :             FILE *fp = fopen(path, "w");
     405            9 :             if (fp) {
     406            9 :                 fprintf(fp, "EMAIL_HOST=imaps://imap.svc.io\n");
     407            9 :                 fprintf(fp, "EMAIL_USER=%s\n", users[i]);
     408            9 :                 fprintf(fp, "EMAIL_PASS=pass\n");
     409            9 :                 fclose(fp);
     410              :             }
     411              :         }
     412              : 
     413            1 :         int count = 0;
     414            1 :         AccountEntry *list = config_list_accounts(&count);
     415            1 :         ASSERT(count == nacct, "many accounts: count=9");
     416            1 :         config_free_account_list(list, count);
     417              : 
     418              :         /* cleanup */
     419           10 :         for (int i = 0; i < nacct; i++) {
     420            9 :             snprintf(path, sizeof(path),
     421              :                      "/tmp/email-cli-test-manyacct/.config/email-cli/accounts/%s/config.ini",
     422              :                      users[i]);
     423            9 :             unlink(path);
     424            9 :             snprintf(path, sizeof(path),
     425              :                      "/tmp/email-cli-test-manyacct/.config/email-cli/accounts/%s",
     426              :                      users[i]);
     427            9 :             rmdir(path);
     428              :         }
     429              :     }
     430              : 
     431            1 :     if (old_home) setenv("HOME", old_home, 1);
     432            0 :     else unsetenv("HOME");
     433            1 :     if (old_xdg) setenv("XDG_CONFIG_HOME", old_xdg, 1);
     434              : }
     435              : 
     436              : /* ── app_settings_set_obfuscation ────────────────────────────────────── */
     437              : 
     438            1 : void test_config_settings(void) {
     439            1 :     char *old_home = getenv("HOME");
     440            1 :     char *old_xdg  = getenv("XDG_CONFIG_HOME");
     441            1 :     setenv("HOME", "/tmp/email-cli-settings-test", 1);
     442            1 :     unsetenv("XDG_CONFIG_HOME");
     443              : 
     444            1 :     int rc = app_settings_set_obfuscation(0);
     445            1 :     ASSERT(rc == 0, "settings: set_obfuscation(0) returns 0");
     446              : 
     447            1 :     int val = app_settings_get_obfuscation();
     448            1 :     ASSERT(val == 0, "settings: get_obfuscation returns 0 after set");
     449              : 
     450            1 :     rc = app_settings_set_obfuscation(1);
     451            1 :     ASSERT(rc == 0, "settings: set_obfuscation(1) returns 0");
     452              : 
     453            1 :     if (old_home) setenv("HOME", old_home, 1); else unsetenv("HOME");
     454            1 :     if (old_xdg)  setenv("XDG_CONFIG_HOME", old_xdg, 1); else unsetenv("XDG_CONFIG_HOME");
     455              : }
     456              : 
     457              : /* ── config_load_account ─────────────────────────────────────────────── */
     458              : 
     459            1 : void test_config_load_account(void) {
     460            1 :     char *old_home = getenv("HOME");
     461            1 :     char *old_xdg  = getenv("XDG_CONFIG_HOME");
     462            1 :     setenv("HOME", "/tmp/email-cli-load-acct-test", 1);
     463            1 :     unsetenv("XDG_CONFIG_HOME");
     464              : 
     465            1 :     Config cfg = {0};
     466            1 :     cfg.host   = strdup("imaps://imap.load-test.com");
     467            1 :     cfg.user   = strdup("load-acct@load-test.com");
     468            1 :     cfg.pass   = strdup("testpass");
     469            1 :     cfg.folder = strdup("INBOX");
     470            1 :     config_save_account(&cfg);
     471            1 :     free(cfg.host); free(cfg.user); free(cfg.pass); free(cfg.folder);
     472              : 
     473            1 :     Config *loaded = config_load_account("load-acct@load-test.com");
     474            1 :     ASSERT(loaded != NULL, "config_load_account: found by user");
     475            1 :     if (loaded) {
     476            1 :         ASSERT(strcmp(loaded->user, "load-acct@load-test.com") == 0,
     477              :                "config_load_account: user matches");
     478            1 :         config_free(loaded);
     479              :     }
     480              : 
     481            1 :     Config *miss = config_load_account("nobody@nowhere.invalid");
     482            1 :     ASSERT(miss == NULL, "config_load_account: missing returns NULL");
     483              : 
     484            1 :     Config *null_r = config_load_account(NULL);
     485            1 :     ASSERT(null_r == NULL, "config_load_account: NULL arg returns NULL");
     486              : 
     487            1 :     Config *empty_r = config_load_account("");
     488            1 :     ASSERT(empty_r == NULL, "config_load_account: empty arg returns NULL");
     489              : 
     490            1 :     if (old_home) setenv("HOME", old_home, 1); else unsetenv("HOME");
     491            1 :     if (old_xdg)  setenv("XDG_CONFIG_HOME", old_xdg, 1); else unsetenv("XDG_CONFIG_HOME");
     492              : }
     493              : 
     494              : /* ── config_migrate_credentials ──────────────────────────────────────── */
     495              : 
     496            1 : void test_config_migrate(void) {
     497            1 :     char *old_home = getenv("HOME");
     498            1 :     char *old_xdg  = getenv("XDG_CONFIG_HOME");
     499            1 :     setenv("HOME", "/tmp/email-cli-migrate-test", 1);
     500            1 :     unsetenv("XDG_CONFIG_HOME");
     501              : 
     502            1 :     Config cfg = {0};
     503            1 :     cfg.host   = strdup("imaps://imap.mig.com");
     504            1 :     cfg.user   = strdup("migrate@mig.com");
     505            1 :     cfg.pass   = strdup("pass");
     506            1 :     cfg.folder = strdup("INBOX");
     507            1 :     config_save_account(&cfg);
     508            1 :     free(cfg.host); free(cfg.user); free(cfg.pass); free(cfg.folder);
     509              : 
     510            1 :     int rc = config_migrate_credentials();
     511            1 :     ASSERT(rc == 0, "config_migrate_credentials: returns 0");
     512              : 
     513              :     /* No accounts → also returns 0 */
     514            1 :     setenv("HOME", "/tmp/email-cli-migrate-empty-test", 1);
     515            1 :     rc = config_migrate_credentials();
     516            1 :     ASSERT(rc == 0, "config_migrate_credentials: empty home returns 0");
     517              : 
     518            1 :     if (old_home) setenv("HOME", old_home, 1); else unsetenv("HOME");
     519            1 :     if (old_xdg)  setenv("XDG_CONFIG_HOME", old_xdg, 1); else unsetenv("XDG_CONFIG_HOME");
     520              : }
        

Generated by: LCOV version 2.0-1