LCOV - code coverage report
Current view: top level - tests/unit - test_local_store.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.4 % 140 135
Test Date: 2026-04-15 21:12:52 Functions: 100.0 % 6 6

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "local_store.h"
       3              : #include "fs_util.h"
       4              : #include "raii.h"
       5              : #include <string.h>
       6              : #include <stdlib.h>
       7              : #include <unistd.h>
       8              : 
       9              : /* ── Helper to set up a clean test environment ────────────────────────── */
      10              : 
      11            4 : static void setup_test_env(const char *home) {
      12            4 :     setenv("HOME", home, 1);
      13            4 :     unsetenv("XDG_DATA_HOME");
      14            4 :     local_store_init("imaps://test.example.com", "testuser");
      15            4 : }
      16              : 
      17              : /* ── Message store tests ─────────────────────────────────────────────── */
      18              : 
      19            1 : void test_local_msg_store(void) {
      20            1 :     char *old_home = getenv("HOME");
      21            1 :     setup_test_env("/tmp/email-cli-store-test");
      22              : 
      23            1 :     const char *folder = "INBOX";
      24            1 :     const int   uid    = 137;
      25              : 
      26              :     /* Pre-clean */
      27            1 :     unlink("/tmp/email-cli-store-test/.local/share/email-cli/accounts/"
      28              :            "testuser/store/INBOX/7/3/137.eml");
      29              : 
      30              :     /* 1. Not stored initially */
      31            1 :     ASSERT(local_msg_exists(folder, uid) == 0,
      32              :            "local_msg_exists: should be 0 before save");
      33              : 
      34              :     /* 2. Save and verify */
      35            1 :     const char *content = "From: test@example.com\r\nDate: Mon, 30 Mar 2026 12:00:00 +0000\r\n"
      36              :                           "Subject: Test\r\n\r\nHello!";
      37            1 :     int rc = local_msg_save(folder, uid, content, strlen(content));
      38            1 :     ASSERT(rc == 0, "local_msg_save: should return 0");
      39            1 :     ASSERT(local_msg_exists(folder, uid) == 1,
      40              :            "local_msg_exists: should be 1 after save");
      41              : 
      42              :     /* 3. Load and verify content */
      43              :     {
      44            2 :         RAII_STRING char *loaded = local_msg_load(folder, uid);
      45            1 :         ASSERT(loaded != NULL, "local_msg_load: should not be NULL");
      46            1 :         ASSERT(strcmp(loaded, content) == 0, "local_msg_load: content mismatch");
      47              :     }
      48              : 
      49              :     /* 4. Different UIDs are independent */
      50            1 :     ASSERT(local_msg_exists(folder, 99) == 0,
      51              :            "local_msg_exists: UID 99 should not exist");
      52              : 
      53              :     /* 5. Reverse digit bucketing: UID 42 → 2/4/ */
      54            1 :     const char *c2 = "Subject: UID 42\r\n\r\nBody";
      55            1 :     local_msg_save(folder, 42, c2, strlen(c2));
      56            1 :     ASSERT(local_msg_exists(folder, 42) == 1,
      57              :            "local_msg_exists: UID 42 after save (bucket 2/4)");
      58              : 
      59              :     /* 6. UID 5 → 5/0/ (single digit pads to 0) */
      60            1 :     const char *c3 = "Subject: UID 5\r\n\r\nBody";
      61            1 :     local_msg_save(folder, 5, c3, strlen(c3));
      62            1 :     ASSERT(local_msg_exists(folder, 5) == 1,
      63              :            "local_msg_exists: UID 5 after save (bucket 5/0)");
      64              : 
      65              :     /* Cleanup */
      66            1 :     unlink("/tmp/email-cli-store-test/.local/share/email-cli/accounts/"
      67              :            "testuser/store/INBOX/7/3/137.eml");
      68            1 :     unlink("/tmp/email-cli-store-test/.local/share/email-cli/accounts/"
      69              :            "testuser/store/INBOX/2/4/42.eml");
      70            1 :     unlink("/tmp/email-cli-store-test/.local/share/email-cli/accounts/"
      71              :            "testuser/store/INBOX/5/0/5.eml");
      72              : 
      73            1 :     if (old_home) setenv("HOME", old_home, 1);
      74            0 :     else unsetenv("HOME");
      75              : }
      76              : 
      77              : /* ── Header eviction tests ───────────────────────────────────────────── */
      78              : 
      79            1 : void test_local_hdr_evict(void) {
      80            1 :     char *old_home = getenv("HOME");
      81            1 :     setup_test_env("/tmp/email-cli-hdr-evict-test");
      82              : 
      83            1 :     const char *folder = "INBOX";
      84              : 
      85            1 :     local_hdr_save(folder, 10, "header-10", 9);
      86            1 :     local_hdr_save(folder, 20, "header-20", 9);
      87              : 
      88            1 :     ASSERT(local_hdr_exists(folder, 10) == 1, "hdr_evict: UID 10 before");
      89            1 :     ASSERT(local_hdr_exists(folder, 20) == 1, "hdr_evict: UID 20 before");
      90              : 
      91            1 :     int keep[] = {20};
      92            1 :     local_hdr_evict_stale(folder, keep, 1);
      93              : 
      94            1 :     ASSERT(local_hdr_exists(folder, 10) == 0, "hdr_evict: UID 10 evicted");
      95            1 :     ASSERT(local_hdr_exists(folder, 20) == 1, "hdr_evict: UID 20 kept");
      96              : 
      97              :     /* Cleanup */
      98            1 :     local_hdr_evict_stale(folder, NULL, 0);
      99              : 
     100            1 :     if (old_home) setenv("HOME", old_home, 1);
     101            0 :     else unsetenv("HOME");
     102              : }
     103              : 
     104              : /* ── Index tests ─────────────────────────────────────────────────────── */
     105              : 
     106            1 : void test_local_index(void) {
     107            1 :     char *old_home = getenv("HOME");
     108            1 :     setup_test_env("/tmp/email-cli-index-test");
     109              : 
     110            1 :     const char *msg =
     111              :         "From: noreply@github.com\r\n"
     112              :         "Date: Tue, 15 Mar 2026 10:30:00 +0100\r\n"
     113              :         "Subject: Test\r\n\r\nBody";
     114              : 
     115            1 :     int rc = local_index_update("INBOX", 42, msg);
     116            1 :     ASSERT(rc == 0, "local_index_update: should return 0");
     117              : 
     118              :     /* Verify from index exists */
     119            1 :     const char *from_path =
     120              :         "/tmp/email-cli-index-test/.local/share/email-cli/accounts/"
     121              :         "testuser/index/from/github.com/noreply";
     122              :     {
     123            2 :         RAII_FILE FILE *fp = fopen(from_path, "r");
     124            1 :         ASSERT(fp != NULL, "from index file should exist");
     125            1 :         if (fp) {
     126            1 :             char line[256];
     127            1 :             ASSERT(fgets(line, sizeof(line), fp) != NULL,
     128              :                    "from index should have a line");
     129            1 :             ASSERT(strstr(line, "INBOX/42") != NULL,
     130              :                    "from index should contain INBOX/42");
     131              :         }
     132              :     }
     133              : 
     134              :     /* Verify date index exists */
     135            1 :     const char *date_path =
     136              :         "/tmp/email-cli-index-test/.local/share/email-cli/accounts/"
     137              :         "testuser/index/date/2026/03/15";
     138              :     {
     139            2 :         RAII_FILE FILE *fp = fopen(date_path, "r");
     140            1 :         ASSERT(fp != NULL, "date index file should exist");
     141            1 :         if (fp) {
     142            1 :             char line[256];
     143            1 :             ASSERT(fgets(line, sizeof(line), fp) != NULL,
     144              :                    "date index should have a line");
     145            1 :             ASSERT(strstr(line, "INBOX/42") != NULL,
     146              :                    "date index should contain INBOX/42");
     147              :         }
     148              :     }
     149              : 
     150              :     /* Duplicate should not be added */
     151            1 :     local_index_update("INBOX", 42, msg);
     152              :     {
     153            2 :         RAII_FILE FILE *fp = fopen(from_path, "r");
     154            1 :         int count = 0;
     155            1 :         char line[256];
     156            2 :         while (fp && fgets(line, sizeof(line), fp)) count++;
     157            1 :         ASSERT(count == 1, "from index should have exactly 1 entry (no dupes)");
     158              :     }
     159              : 
     160              :     /* Cleanup */
     161            1 :     if (from_path) unlink(from_path);
     162            1 :     if (date_path) unlink(date_path);
     163              : 
     164            1 :     if (old_home) setenv("HOME", old_home, 1);
     165            0 :     else unsetenv("HOME");
     166              : }
     167              : 
     168              : /* ── Manifest tests ──────────────────────────────────────────────────── */
     169              : 
     170            1 : void test_manifest(void) {
     171            1 :     char *old_home = getenv("HOME");
     172            1 :     setup_test_env("/tmp/email-cli-manifest-test");
     173              : 
     174              :     /* Pre-clean */
     175            1 :     unlink("/tmp/email-cli-manifest-test/.local/share/email-cli/"
     176              :            "accounts/testuser/manifests/INBOX.tsv");
     177              : 
     178              :     /* 1. Load non-existent manifest returns NULL */
     179            1 :     Manifest *m = manifest_load("INBOX");
     180            1 :     ASSERT(m == NULL, "manifest_load: NULL for missing file");
     181              : 
     182              :     /* 2. Create manifest, add entries, save */
     183            1 :     m = calloc(1, sizeof(Manifest));
     184            1 :     ASSERT(m != NULL, "manifest: calloc");
     185            1 :     manifest_upsert(m, 42,   strdup("Alice <alice@example.com>"),
     186              :                               strdup("Hello World"),
     187              :                               strdup("2024-03-15 10:00"), MSG_FLAG_UNSEEN);
     188            1 :     manifest_upsert(m, 137,  strdup("Bob <bob@test.org>"),
     189              :                               strdup("Re: Meeting"),
     190              :                               strdup("2024-03-16 14:30"), 0);
     191            1 :     ASSERT(m->count == 2, "manifest: 2 entries after upsert");
     192              : 
     193            1 :     int rc = manifest_save("INBOX", m);
     194            1 :     ASSERT(rc == 0, "manifest_save: returns 0");
     195              : 
     196              :     /* 3. Load back and verify */
     197            1 :     manifest_free(m);
     198            1 :     m = manifest_load("INBOX");
     199            1 :     ASSERT(m != NULL, "manifest_load: not NULL after save");
     200            1 :     ASSERT(m->count == 2, "manifest_load: 2 entries");
     201              : 
     202            1 :     ManifestEntry *e42 = manifest_find(m, 42);
     203            1 :     ASSERT(e42 != NULL, "manifest_find: UID 42 found");
     204            1 :     ASSERT(strcmp(e42->from, "Alice <alice@example.com>") == 0,
     205              :            "manifest: UID 42 from correct");
     206            1 :     ASSERT(strcmp(e42->subject, "Hello World") == 0,
     207              :            "manifest: UID 42 subject correct");
     208            1 :     ASSERT(strcmp(e42->date, "2024-03-15 10:00") == 0,
     209              :            "manifest: UID 42 date correct");
     210              : 
     211            1 :     ManifestEntry *e137 = manifest_find(m, 137);
     212            1 :     ASSERT(e137 != NULL, "manifest_find: UID 137 found");
     213            1 :     ASSERT(strcmp(e137->subject, "Re: Meeting") == 0,
     214              :            "manifest: UID 137 subject correct");
     215              : 
     216              :     /* 4. Upsert updates existing entry */
     217            1 :     manifest_upsert(m, 42, strdup("Alice Updated"),
     218              :                            strdup("Updated Subject"),
     219              :                            strdup("2024-03-15 11:00"), 0 /* no flags */);
     220            1 :     ASSERT(m->count == 2, "manifest: still 2 after upsert-update");
     221            1 :     e42 = manifest_find(m, 42);
     222            1 :     ASSERT(strcmp(e42->subject, "Updated Subject") == 0,
     223              :            "manifest: upsert updated subject");
     224              : 
     225              :     /* 5. manifest_find returns NULL for missing UID */
     226            1 :     ASSERT(manifest_find(m, 999) == NULL,
     227              :            "manifest_find: NULL for missing UID");
     228              : 
     229              :     /* 6. manifest_retain keeps only specified UIDs */
     230            1 :     int keep[] = {137};
     231            1 :     manifest_retain(m, keep, 1);
     232            1 :     ASSERT(m->count == 1, "manifest_retain: 1 entry after retain");
     233            1 :     ASSERT(manifest_find(m, 137) != NULL, "manifest_retain: UID 137 kept");
     234            1 :     ASSERT(manifest_find(m, 42) == NULL, "manifest_retain: UID 42 removed");
     235              : 
     236              :     /* 7. Nested folder manifest */
     237            1 :     Manifest *m2 = calloc(1, sizeof(Manifest));
     238            1 :     manifest_upsert(m2, 1, strdup("Test"), strdup("Nested"), strdup("2024-01-01 00:00"), 0 /* no flags */);
     239            1 :     ASSERT(manifest_save("munka/ai", m2) == 0, "manifest_save: nested folder");
     240            1 :     manifest_free(m2);
     241            1 :     m2 = manifest_load("munka/ai");
     242            1 :     ASSERT(m2 != NULL, "manifest_load: nested folder");
     243            1 :     ASSERT(m2->count == 1, "manifest: nested has 1 entry");
     244            1 :     manifest_free(m2);
     245              : 
     246              :     /* Cleanup */
     247            1 :     manifest_free(m);
     248            1 :     if (old_home) setenv("HOME", old_home, 1);
     249            0 :     else unsetenv("HOME");
     250              : }
     251              : 
     252              : /* ── UI preferences tests ────────────────────────────────────────────── */
     253              : 
     254            1 : void test_ui_prefs(void) {
     255            1 :     char *old_home = getenv("HOME");
     256            1 :     setenv("HOME", "/tmp/email-cli-ui-pref-test-home", 1);
     257            1 :     unsetenv("XDG_DATA_HOME");
     258            1 :     unlink("/tmp/email-cli-ui-pref-test-home/.local/share/email-cli/ui.ini");
     259              : 
     260            1 :     ASSERT(ui_pref_get_int("folder_view_mode", 1) == 1,
     261              :            "ui_pref_get_int: missing key should return default 1");
     262            1 :     ASSERT(ui_pref_get_int("folder_view_mode", 0) == 0,
     263              :            "ui_pref_get_int: missing key should return default 0");
     264              : 
     265            1 :     ASSERT(ui_pref_set_int("folder_view_mode", 0) == 0,
     266              :            "ui_pref_set_int: should return 0");
     267            1 :     ASSERT(ui_pref_get_int("folder_view_mode", 1) == 0,
     268              :            "ui_pref_get_int: should return stored value 0");
     269              : 
     270            1 :     ASSERT(ui_pref_set_int("folder_view_mode", 1) == 0,
     271              :            "ui_pref_set_int: overwrite should return 0");
     272            1 :     ASSERT(ui_pref_get_int("folder_view_mode", 0) == 1,
     273              :            "ui_pref_get_int: should return updated value 1");
     274              : 
     275            1 :     ASSERT(ui_pref_set_int("other_pref", 42) == 0,
     276              :            "ui_pref_set_int: second key should return 0");
     277            1 :     ASSERT(ui_pref_get_int("folder_view_mode", 0) == 1,
     278              :            "ui_pref_get_int: first key intact");
     279            1 :     ASSERT(ui_pref_get_int("other_pref", 0) == 42,
     280              :            "ui_pref_get_int: second key should return 42");
     281              : 
     282            1 :     ASSERT(ui_pref_get_int("no_such_key", 7) == 7,
     283              :            "ui_pref_get_int: unknown key should return default");
     284              : 
     285            1 :     unlink("/tmp/email-cli-ui-pref-test-home/.local/share/email-cli/ui.ini");
     286              : 
     287            1 :     if (old_home) setenv("HOME", old_home, 1);
     288            0 :     else unsetenv("HOME");
     289              : }
        

Generated by: LCOV version 2.0-1