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

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "logger.h"
       3              : #include "fs_util.h"
       4              : #include "raii.h"
       5              : #include <stdio.h>
       6              : #include <string.h>
       7              : #include <stdlib.h>
       8              : #include <unistd.h>
       9              : #include <sys/stat.h>
      10              : 
      11            1 : void test_logger(void) {
      12            1 :     const char *test_log_dir = "/tmp/tg-cli-log-test";
      13            1 :     const char *test_log_file = "/tmp/tg-cli-log-test/test.log";
      14            1 :     const char *rotated_log_file = "/tmp/tg-cli-log-test/test.log.1";
      15              : 
      16              :     // 1. Prepare directory
      17            1 :     fs_mkdir_p(test_log_dir, 0700);
      18              : 
      19              :     // 2. Test Init
      20            1 :     int res = logger_init(test_log_file, LOG_DEBUG);
      21            1 :     ASSERT(res == 0, "logger_init should return 0");
      22              : 
      23              :     // 3. Test Logging at various levels
      24            1 :     logger_log(LOG_DEBUG, "Test debug message");
      25            1 :     logger_log(LOG_INFO, "Test info message");
      26            1 :     logger_log(LOG_WARN, "Test warn message");
      27            1 :     logger_log(LOG_ERROR, "Test error message");
      28              : 
      29              :     // 4. Verify file exists and has content
      30              :     struct stat st;
      31            1 :     ASSERT(stat(test_log_file, &st) == 0, "Log file should exist");
      32            1 :     ASSERT(st.st_size > 0, "Log file should not be empty");
      33              : 
      34              :     // 4a. FEAT-21: log file must be mode 0600 (never world-readable)
      35            1 :     ASSERT((st.st_mode & 0777) == 0600,
      36              :            "Log file must have permissions 0600");
      37              : 
      38              :     // 4b. FEAT-21: logs directory must be mode 0700
      39              :     struct stat dir_st;
      40            1 :     ASSERT(stat(test_log_dir, &dir_st) == 0, "Log directory should exist");
      41            1 :     ASSERT((dir_st.st_mode & 0777) == 0700,
      42              :            "Log directory must have permissions 0700");
      43              : 
      44              :     // 5. Test level filtering: reinit with LOG_ERROR, lower-level messages suppressed
      45            1 :     logger_close();
      46            1 :     res = logger_init(test_log_file, LOG_ERROR);
      47            1 :     ASSERT(res == 0, "logger_init with LOG_ERROR should succeed");
      48            1 :     logger_log(LOG_DEBUG, "This should be filtered out");
      49            1 :     logger_log(LOG_INFO,  "This should be filtered out");
      50            1 :     logger_log(LOG_WARN,  "This should be filtered out");
      51              : 
      52              :     // 6. Test Clean Logs
      53              :     {
      54            2 :         RAII_FILE FILE *f = fopen("/tmp/tg-cli-log-test/session.log.old", "w");
      55            1 :         if (f) {
      56            1 :             fprintf(f, "old data");
      57              :         }
      58              :     }
      59            1 :     res = logger_clean_logs(test_log_dir);
      60            1 :     ASSERT(res == 0, "logger_clean_logs should return 0");
      61            1 :     ASSERT(access("/tmp/tg-cli-log-test/session.log.old", F_OK) == -1,
      62              :            "Old log should be deleted");
      63              : 
      64              :     // 7. Close and verify logger_log with NULL fp does not crash
      65            1 :     logger_close();
      66            1 :     logger_log(LOG_ERROR, "Should not crash with NULL fp");
      67              : 
      68              :     // 8. Test logger_init with invalid (non-existent) path
      69            1 :     res = logger_init("/nonexistent/dir/path/test.log", LOG_INFO);
      70            1 :     ASSERT(res == -1, "logger_init should return -1 for invalid path");
      71              : 
      72              :     // 9. Test logger_clean_logs with non-existent directory
      73            1 :     res = logger_clean_logs("/nonexistent/dir/path");
      74            1 :     ASSERT(res == -1, "logger_clean_logs should return -1 for non-existent dir");
      75              : 
      76              :     // QA-16 guard: calling logger_init twice must not leak the previous
      77              :     // path+fd. Valgrind would flag these as lost blocks without the fix.
      78            1 :     res = logger_init(test_log_file, LOG_INFO);
      79            1 :     ASSERT(res == 0, "logger_init first call ok");
      80            1 :     res = logger_init(test_log_file, LOG_INFO);
      81            1 :     ASSERT(res == 0, "logger_init second call ok — no leak");
      82            1 :     logger_close();
      83              : 
      84              :     // 4c. FEAT-21: opening a pre-existing file with wrong permissions fixes them
      85            1 :     unlink(test_log_file);
      86              :     {
      87            2 :         RAII_FILE FILE *f = fopen(test_log_file, "w");
      88            1 :         if (f) fprintf(f, "existing\n");
      89              :     }
      90            1 :     chmod(test_log_file, 0644);   /* set overly-permissive mode */
      91            1 :     res = logger_init(test_log_file, LOG_INFO);
      92            1 :     ASSERT(res == 0, "logger_init should succeed on pre-existing file");
      93              :     {
      94              :         struct stat fix_st;
      95            1 :         ASSERT(stat(test_log_file, &fix_st) == 0, "Pre-existing log should exist");
      96            1 :         ASSERT((fix_st.st_mode & 0777) == 0600,
      97              :                "Pre-existing log must be fixed to 0600 by logger_init");
      98              :     }
      99            1 :     logger_close();
     100              : 
     101              :     // 10. Test log rotation: create a file > 5MB, then init should rotate it
     102            1 :     unlink(test_log_file);
     103              :     {
     104            2 :         RAII_FILE FILE *big = fopen(test_log_file, "wb");
     105            1 :         if (big) {
     106            1 :             fseek(big, 5 * 1024 * 1024, SEEK_SET);
     107            1 :             fputc('\0', big);
     108              :         }
     109              :     }
     110            1 :     res = logger_init(test_log_file, LOG_INFO);
     111            1 :     ASSERT(res == 0, "logger_init should succeed after rotating oversized log");
     112            1 :     ASSERT(stat(test_log_file, &st) == 0, "Log file should exist after rotation");
     113            1 :     ASSERT(st.st_size < 5 * 1024 * 1024,
     114              :            "Current log should be small after rotation");
     115            1 :     ASSERT(access(rotated_log_file, F_OK) == 0,
     116              :            "Rotated log file should exist");
     117            1 :     logger_close();
     118              : 
     119              :     // Manual cleanup
     120            1 :     unlink(test_log_file);
     121            1 :     unlink(rotated_log_file);
     122            1 :     rmdir(test_log_dir);
     123              : }
        

Generated by: LCOV version 2.0-1