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

            Line data    Source code
       1              : /**
       2              :  * @file test_logout_rpc.c
       3              :  * @brief TEST-23 — functional tests for auth.logOut RPC + session wipe.
       4              :  *
       5              :  * Covers two scenarios:
       6              :  *   1. Happy path: server returns auth.loggedOut → RPC fires once, session.bin
       7              :  *      removed, auth_logout() returns.
       8              :  *   2. Error path: server returns rpc_error → session.bin still wiped
       9              :  *      (best-effort logout).
      10              :  */
      11              : 
      12              : #include "test_helpers.h"
      13              : 
      14              : #include "mock_socket.h"
      15              : #include "mock_tel_server.h"
      16              : 
      17              : #include "infrastructure/auth_logout.h"
      18              : #include "mtproto_session.h"
      19              : #include "transport.h"
      20              : #include "api_call.h"
      21              : #include "app/session_store.h"
      22              : #include "tl_serial.h"
      23              : #include "tl_registry.h"
      24              : 
      25              : #include <stdio.h>
      26              : #include <stdlib.h>
      27              : #include <string.h>
      28              : #include <sys/stat.h>
      29              : #include <unistd.h>
      30              : 
      31              : /* ---- helpers ---- */
      32              : 
      33            4 : static void with_tmp_home_logout(const char *tag) {
      34              :     char tmp[256];
      35            4 :     snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-logout-%s", tag);
      36              :     char bin[512];
      37            4 :     snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
      38            4 :     (void)unlink(bin);
      39            4 :     setenv("HOME", tmp, 1);
      40              :     /* CI runners (GitHub Actions) may export XDG_CONFIG_HOME, which would
      41              :      * make platform_config_dir() ignore our redirected HOME. Force the
      42              :      * HOME-based fallback so prod code and session_bin_exists() agree. */
      43            4 :     unsetenv("XDG_CONFIG_HOME");
      44            4 : }
      45              : 
      46            8 : static int session_bin_exists(void) {
      47              :     char path[512];
      48            8 :     const char *home = getenv("HOME");
      49            8 :     snprintf(path, sizeof(path), "%s/.config/tg-cli/session.bin", home);
      50              :     struct stat st;
      51            8 :     return stat(path, &st) == 0;
      52              : }
      53              : 
      54              : /** Connect a fresh Transport to the mock-socket loopback. */
      55            4 : static void connect_mock_logout(Transport *t) {
      56            4 :     transport_init(t);
      57            4 :     ASSERT(transport_connect(t, "127.0.0.1", 443) == 0,
      58              :            "transport connects for logout test");
      59              : }
      60              : 
      61              : /** Populate an ApiConfig with fake credentials. */
      62            4 : static void init_cfg_logout(ApiConfig *cfg) {
      63            4 :     api_config_init(cfg);
      64            4 :     cfg->api_id   = 12345;
      65            4 :     cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
      66            4 : }
      67              : 
      68              : /* ================================================================ */
      69              : /* Responders                                                       */
      70              : /* ================================================================ */
      71              : 
      72              : /** Happy path: auth.loggedOut#c3a2835f with flags=0. */
      73            2 : static void on_logout_ok(MtRpcContext *ctx) {
      74              :     TlWriter w;
      75            2 :     tl_writer_init(&w);
      76            2 :     tl_write_uint32(&w, CRC_auth_loggedOut);   /* 0xc3a2835f */
      77            2 :     tl_write_uint32(&w, 0);                    /* flags = 0 (no future_auth_token) */
      78            2 :     mt_server_reply_result(ctx, w.data, w.len);
      79            2 :     tl_writer_free(&w);
      80            2 : }
      81              : 
      82              : /** Error path: generic rpc_error 500 INTERNAL. */
      83            2 : static void on_logout_error(MtRpcContext *ctx) {
      84            2 :     mt_server_reply_error(ctx, 500, "INTERNAL");
      85            2 : }
      86              : 
      87              : /* ================================================================ */
      88              : /* Test cases                                                       */
      89              : /* ================================================================ */
      90              : 
      91              : /**
      92              :  * @brief TEST-23a — happy path: auth.loggedOut returned, session.bin wiped.
      93              :  */
      94            2 : static void test_logout_rpc_happy(void) {
      95            2 :     with_tmp_home_logout("happy");
      96            2 :     mt_server_init();
      97            2 :     mt_server_reset();
      98            2 :     ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed session");
      99              : 
     100              :     /* Confirm session.bin was created by the seed. */
     101            2 :     ASSERT(session_bin_exists(), "session.bin exists before logout");
     102              : 
     103            2 :     mt_server_expect(CRC_auth_logOut, on_logout_ok, NULL);
     104              : 
     105            2 :     ApiConfig cfg; init_cfg_logout(&cfg);
     106            2 :     MtProtoSession s; mtproto_session_init(&s);
     107            2 :     int dc = 0;
     108            2 :     ASSERT(session_store_load(&s, &dc) == 0, "session loaded");
     109              : 
     110            2 :     Transport t; connect_mock_logout(&t);
     111              : 
     112              :     /* auth_logout() = auth_logout_rpc() + session_store_clear(). */
     113            2 :     auth_logout(&cfg, &s, &t);
     114              : 
     115              :     /* Responder must have fired exactly once. */
     116            2 :     ASSERT(mt_server_request_crc_count(CRC_auth_logOut) == 1,
     117              :            "auth.logOut sent exactly once");
     118              : 
     119              :     /* session.bin must be gone. */
     120            2 :     ASSERT(!session_bin_exists(), "session.bin removed after logout");
     121              : 
     122            2 :     transport_close(&t);
     123            2 :     mt_server_reset();
     124              : }
     125              : 
     126              : /**
     127              :  * @brief TEST-23b — error path: server returns rpc_error, session.bin still
     128              :  *        wiped (best-effort logout).
     129              :  */
     130            2 : static void test_logout_rpc_error_still_clears_session(void) {
     131            2 :     with_tmp_home_logout("error");
     132            2 :     mt_server_init();
     133            2 :     mt_server_reset();
     134            2 :     ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed session");
     135              : 
     136            2 :     ASSERT(session_bin_exists(), "session.bin exists before logout");
     137              : 
     138            2 :     mt_server_expect(CRC_auth_logOut, on_logout_error, NULL);
     139              : 
     140            2 :     ApiConfig cfg; init_cfg_logout(&cfg);
     141            2 :     MtProtoSession s; mtproto_session_init(&s);
     142            2 :     int dc = 0;
     143            2 :     ASSERT(session_store_load(&s, &dc) == 0, "session loaded");
     144              : 
     145            2 :     Transport t; connect_mock_logout(&t);
     146              : 
     147              :     /* auth_logout() must wipe the file even when the RPC fails. */
     148            2 :     auth_logout(&cfg, &s, &t);
     149              : 
     150              :     /* Responder fired. */
     151            2 :     ASSERT(mt_server_request_crc_count(CRC_auth_logOut) == 1,
     152              :            "auth.logOut sent exactly once (error variant)");
     153              : 
     154              :     /* File still gone regardless of RPC error. */
     155            2 :     ASSERT(!session_bin_exists(),
     156              :            "session.bin removed even after RPC error (best-effort)");
     157              : 
     158            2 :     transport_close(&t);
     159            2 :     mt_server_reset();
     160              : }
     161              : 
     162              : /* ================================================================ */
     163              : /* Suite entry point                                                */
     164              : /* ================================================================ */
     165              : 
     166            2 : void run_logout_rpc_tests(void) {
     167            2 :     RUN_TEST(test_logout_rpc_happy);
     168            2 :     RUN_TEST(test_logout_rpc_error_still_clears_session);
     169            2 : }
        

Generated by: LCOV version 2.0-1