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

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "cache_store.h"
       3              : #include "fs_util.h"
       4              : #include "raii.h"
       5              : #include <string.h>
       6              : #include <stdlib.h>
       7              : #include <unistd.h>
       8              : #include <sys/stat.h>
       9              : 
      10            1 : static void test_cache_basic(void) {
      11            1 :     char *old_home = getenv("HOME");
      12            1 :     setenv("HOME", "/tmp/tg-cli-cache-test-home", 1);
      13              : 
      14            1 :     const char *category = "messages";
      15            1 :     const char *key      = "12345/42";
      16              : 
      17              :     /* 1. Not cached initially */
      18            1 :     ASSERT(cache_exists(category, key) == 0, "cache_exists: should be 0 before save");
      19              : 
      20              :     {
      21            2 :         RAII_STRING char *loaded = cache_load(category, key);
      22            1 :         ASSERT(loaded == NULL, "cache_load: should return NULL before save");
      23              :     }
      24              : 
      25              :     /* 2. Save and verify existence */
      26            1 :     const char *content = "{\"text\":\"Cache Test\",\"chat_id\":12345}";
      27            1 :     int rc = cache_save(category, key, content, strlen(content));
      28            1 :     ASSERT(rc == 0, "cache_save: should return 0 on success");
      29            1 :     ASSERT(cache_exists(category, key) == 1, "cache_exists: should be 1 after save");
      30              : 
      31              :     /* 3. Load and verify content */
      32              :     {
      33            2 :         RAII_STRING char *loaded = cache_load(category, key);
      34            1 :         ASSERT(loaded != NULL, "cache_load: should not be NULL after save");
      35            1 :         ASSERT(strcmp(loaded, content) == 0, "cache_load: content mismatch");
      36              :     }
      37              : 
      38              :     /* 4. Overwrite with new content */
      39            1 :     const char *content2 = "{\"text\":\"Updated\",\"chat_id\":12345}";
      40            1 :     rc = cache_save(category, key, content2, strlen(content2));
      41            1 :     ASSERT(rc == 0, "cache_save: overwrite should return 0");
      42              :     {
      43            2 :         RAII_STRING char *loaded = cache_load(category, key);
      44            1 :         ASSERT(loaded != NULL, "cache_load: should not be NULL after overwrite");
      45            1 :         ASSERT(strcmp(loaded, content2) == 0, "cache_load: overwritten content mismatch");
      46              :     }
      47              : 
      48              :     /* 5. Different keys are independent */
      49            1 :     ASSERT(cache_exists(category, "12345/99") == 0, "cache_exists: different key should not exist");
      50              : 
      51              :     /* Cleanup */
      52            2 :     RAII_STRING char *path = cache_path(category, key);
      53            1 :     if (path) unlink(path);
      54              : 
      55            1 :     if (old_home) setenv("HOME", old_home, 1);
      56            0 :     else unsetenv("HOME");
      57              : }
      58              : 
      59              : /* ---- cache_load: NULL/missing paths ---- */
      60            1 : static void test_cache_load_missing(void) {
      61            1 :     char *old_home = getenv("HOME");
      62            1 :     setenv("HOME", "/tmp/tg-cli-cache-missing", 1);
      63              : 
      64              :     /* no file exists → NULL */
      65            2 :     RAII_STRING char *v = cache_load("messages", "nonexistent/9999");
      66            1 :     ASSERT(v == NULL, "cache_load: missing file must return NULL");
      67              : 
      68            1 :     if (old_home) setenv("HOME", old_home, 1);
      69            0 :     else unsetenv("HOME");
      70              : }
      71              : 
      72              : /* ---- cache_save: failing to create parent dir returns -1 ---- */
      73            1 : static void test_cache_save_mkdir_fails(void) {
      74            1 :     char *old_home = getenv("HOME");
      75              :     /* Create a regular file where tg-cli would place its cache dir so
      76              :      * fs_mkdir_p fails. */
      77            1 :     const char *bad_home = "/tmp/tg-cli-cache-bad-home";
      78              :     /* Clean slate */
      79              :     {
      80              :         char path[256];
      81            1 :         snprintf(path, sizeof(path), "%s/.cache/tg-cli/media", bad_home);
      82            1 :         (void)remove(path);
      83              :     }
      84            1 :     mkdir(bad_home, 0700);
      85              :     char file_in_way[256];
      86            1 :     snprintf(file_in_way, sizeof(file_in_way), "%s/.cache", bad_home);
      87              :     /* Make ".cache" a regular file → mkdir_p will fail */
      88            1 :     FILE *f = fopen(file_in_way, "w");
      89            1 :     if (f) { fputs("x", f); fclose(f); }
      90              : 
      91            1 :     setenv("HOME", bad_home, 1);
      92              : 
      93            1 :     int rc = cache_save("media", "key1", "data", 4);
      94            1 :     ASSERT(rc == -1, "cache_save: must fail when parent cannot be created");
      95              : 
      96              :     /* cleanup */
      97            1 :     unlink(file_in_way);
      98            1 :     rmdir(bad_home);
      99              : 
     100            1 :     if (old_home) setenv("HOME", old_home, 1);
     101            0 :     else unsetenv("HOME");
     102              : }
     103              : 
     104              : /* ---- cache_evict_stale: removes entries not in keep-list ---- */
     105            1 : static void test_cache_evict_stale(void) {
     106            1 :     char *old_home = getenv("HOME");
     107            1 :     setenv("HOME", "/tmp/tg-cli-cache-evict", 1);
     108              : 
     109              :     /* Populate 3 entries */
     110            1 :     const char *cat = "messages";
     111            1 :     cache_save(cat, "a", "aa", 2);
     112            1 :     cache_save(cat, "b", "bb", 2);
     113            1 :     cache_save(cat, "c", "cc", 2);
     114            1 :     ASSERT(cache_exists(cat, "a") == 1, "a exists");
     115            1 :     ASSERT(cache_exists(cat, "b") == 1, "b exists");
     116            1 :     ASSERT(cache_exists(cat, "c") == 1, "c exists");
     117              : 
     118              :     /* Keep only "b" — "a" and "c" should be evicted */
     119            1 :     const char *keep[] = {"b"};
     120            1 :     cache_evict_stale(cat, keep, 1);
     121              : 
     122            1 :     ASSERT(cache_exists(cat, "a") == 0, "a evicted");
     123            1 :     ASSERT(cache_exists(cat, "b") == 1, "b preserved");
     124            1 :     ASSERT(cache_exists(cat, "c") == 0, "c evicted");
     125              : 
     126              :     /* cleanup */
     127            2 :     RAII_STRING char *pb = cache_path(cat, "b");
     128            1 :     if (pb) unlink(pb);
     129              : 
     130            1 :     if (old_home) setenv("HOME", old_home, 1);
     131            0 :     else unsetenv("HOME");
     132              : }
     133              : 
     134              : /* ---- cache_evict_stale: empty keep list removes everything ---- */
     135            1 : static void test_cache_evict_stale_empty_keep(void) {
     136            1 :     char *old_home = getenv("HOME");
     137            1 :     setenv("HOME", "/tmp/tg-cli-cache-evict2", 1);
     138              : 
     139            1 :     const char *cat = "media";
     140            1 :     cache_save(cat, "x", "x", 1);
     141            1 :     cache_save(cat, "y", "y", 1);
     142            1 :     ASSERT(cache_exists(cat, "x") == 1, "x exists");
     143              : 
     144            1 :     const char *keep[] = {"__sentinel__"}; /* keep list with no matches */
     145            1 :     cache_evict_stale(cat, keep, 1);
     146              : 
     147            1 :     ASSERT(cache_exists(cat, "x") == 0, "x evicted");
     148            1 :     ASSERT(cache_exists(cat, "y") == 0, "y evicted");
     149              : 
     150            1 :     if (old_home) setenv("HOME", old_home, 1);
     151            0 :     else unsetenv("HOME");
     152              : }
     153              : 
     154              : /* ---- cache_evict_stale: nonexistent category is a no-op ---- */
     155            1 : static void test_cache_evict_stale_no_dir(void) {
     156            1 :     char *old_home = getenv("HOME");
     157            1 :     setenv("HOME", "/tmp/tg-cli-cache-evict-nodir", 1);
     158              : 
     159            1 :     const char *keep[] = {"a"};
     160              :     /* Directory does not exist → function should return without error */
     161            1 :     cache_evict_stale("nonexistent_category", keep, 1);
     162              : 
     163            1 :     if (old_home) setenv("HOME", old_home, 1);
     164            0 :     else unsetenv("HOME");
     165            1 : }
     166              : 
     167            1 : void test_cache_store(void) {
     168            1 :     RUN_TEST(test_cache_basic);
     169            1 :     RUN_TEST(test_cache_load_missing);
     170            1 :     RUN_TEST(test_cache_save_mkdir_fails);
     171            1 :     RUN_TEST(test_cache_evict_stale);
     172            1 :     RUN_TEST(test_cache_evict_stale_empty_keep);
     173            1 :     RUN_TEST(test_cache_evict_stale_no_dir);
     174            1 : }
        

Generated by: LCOV version 2.0-1