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

            Line data    Source code
       1              : /**
       2              :  * @file test_domain_search.c
       3              :  * @brief Unit tests for domain_search_peer / domain_search_global.
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "domain/read/search.h"
       8              : #include "tl_serial.h"
       9              : #include "tl_registry.h"
      10              : #include "mock_socket.h"
      11              : #include "mock_crypto.h"
      12              : #include "mtproto_session.h"
      13              : #include "transport.h"
      14              : #include "api_call.h"
      15              : 
      16              : #include <stdlib.h>
      17              : #include <string.h>
      18              : 
      19            4 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
      20              :                                           uint8_t *out, size_t *out_len) {
      21            4 :     TlWriter w; tl_writer_init(&w);
      22            4 :     uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
      23            4 :     uint8_t header[32] = {0};
      24            4 :     uint32_t plen32 = (uint32_t)plen;
      25            4 :     memcpy(header + 28, &plen32, 4);
      26            4 :     tl_write_raw(&w, header, 32);
      27            4 :     tl_write_raw(&w, payload, plen);
      28            4 :     size_t enc = w.len - 24;
      29            4 :     if (enc % 16 != 0) {
      30            4 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
      31              :     }
      32            4 :     out[0] = (uint8_t)(w.len / 4);
      33            4 :     memcpy(out + 1, w.data, w.len);
      34            4 :     *out_len = 1 + w.len;
      35            4 :     tl_writer_free(&w);
      36            4 : }
      37              : 
      38            4 : static void fix_session(MtProtoSession *s) {
      39            4 :     mtproto_session_init(s);
      40            4 :     s->session_id = 0; /* match the zero session_id in fake encrypted frames */
      41            4 :     uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
      42            4 :     mtproto_session_set_salt(s, 0x1112131415161718ULL);
      43            4 : }
      44            4 : static void fix_transport(Transport *t) {
      45            4 :     transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
      46            4 : }
      47            4 : static void fix_cfg(ApiConfig *cfg) {
      48            4 :     api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
      49            4 : }
      50              : 
      51            3 : static size_t make_messages_response(uint8_t *buf, size_t max, int32_t id) {
      52            3 :     TlWriter w; tl_writer_init(&w);
      53            3 :     tl_write_uint32(&w, TL_messages_messages);
      54            3 :     tl_write_uint32(&w, TL_vector);
      55            3 :     tl_write_uint32(&w, 1);
      56            3 :     tl_write_uint32(&w, TL_messageEmpty);
      57            3 :     tl_write_uint32(&w, 0);
      58            3 :     tl_write_int32 (&w, id);
      59            3 :     size_t n = w.len < max ? w.len : max;
      60            3 :     memcpy(buf, w.data, n); tl_writer_free(&w);
      61            3 :     return n;
      62              : }
      63              : 
      64            1 : static void test_search_global(void) {
      65            1 :     mock_socket_reset(); mock_crypto_reset();
      66              : 
      67              :     uint8_t payload[256];
      68            1 :     size_t plen = make_messages_response(payload, sizeof(payload), 55);
      69            1 :     uint8_t resp[1024]; size_t rlen = 0;
      70            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      71            1 :     mock_socket_set_response(resp, rlen);
      72              : 
      73              :     MtProtoSession s; Transport t; ApiConfig cfg;
      74            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      75              : 
      76            1 :     HistoryEntry e[5] = {0}; int n = 0;
      77            1 :     int rc = domain_search_global(&cfg, &s, &t, "hello", 5, e, &n);
      78            1 :     ASSERT(rc == 0, "global search parsed");
      79            1 :     ASSERT(n == 1, "one entry");
      80            1 :     ASSERT(e[0].id == 55, "id matches");
      81              : }
      82              : 
      83            1 : static void test_search_peer(void) {
      84            1 :     mock_socket_reset(); mock_crypto_reset();
      85              : 
      86              :     uint8_t payload[256];
      87            1 :     size_t plen = make_messages_response(payload, sizeof(payload), 77);
      88            1 :     uint8_t resp[1024]; size_t rlen = 0;
      89            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      90            1 :     mock_socket_set_response(resp, rlen);
      91              : 
      92              :     MtProtoSession s; Transport t; ApiConfig cfg;
      93            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      94              : 
      95            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
      96            1 :     HistoryEntry e[5] = {0}; int n = 0;
      97            1 :     int rc = domain_search_peer(&cfg, &s, &t, &peer, "needle", 5, e, &n);
      98            1 :     ASSERT(rc == 0, "per-peer search parsed");
      99            1 :     ASSERT(e[0].id == 77, "id matches");
     100              : }
     101              : 
     102            1 : static void test_search_rpc_error(void) {
     103            1 :     mock_socket_reset(); mock_crypto_reset();
     104              : 
     105            1 :     TlWriter w; tl_writer_init(&w);
     106            1 :     tl_write_uint32(&w, TL_rpc_error);
     107            1 :     tl_write_int32(&w, 400);
     108            1 :     tl_write_string(&w, "QUERY_TOO_SHORT");
     109            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     110            1 :     size_t plen = w.len; tl_writer_free(&w);
     111              : 
     112            1 :     uint8_t resp[512]; size_t rlen = 0;
     113            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     114            1 :     mock_socket_set_response(resp, rlen);
     115              : 
     116              :     MtProtoSession s; Transport t; ApiConfig cfg;
     117            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     118              : 
     119            1 :     HistoryEntry e[3] = {0}; int n = 0;
     120            1 :     int rc = domain_search_global(&cfg, &s, &t, "q", 3, e, &n);
     121            1 :     ASSERT(rc != 0, "RPC error propagates");
     122              : }
     123              : 
     124            1 : static void test_search_null_args(void) {
     125            1 :     HistoryEntry e[1]; int n = 0;
     126            1 :     ASSERT(domain_search_global(NULL, NULL, NULL, "x", 3, e, &n) == -1, "global nulls");
     127            1 :     ASSERT(domain_search_peer(NULL, NULL, NULL, NULL, "x", 3, e, &n) == -1, "peer nulls");
     128              : }
     129              : 
     130              : /* Validate FEAT-09: per-peer search called with a user peer (not just self).
     131              :  * This exercises the HISTORY_PEER_USER branch of write_input_peer inside
     132              :  * domain_search_peer — the path taken by `search @user <query>` in tg-tui. */
     133            1 : static void test_search_peer_user_kind(void) {
     134            1 :     mock_socket_reset(); mock_crypto_reset();
     135              : 
     136              :     uint8_t payload[256];
     137            1 :     size_t plen = make_messages_response(payload, sizeof(payload), 99);
     138            1 :     uint8_t resp[1024]; size_t rlen = 0;
     139            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     140            1 :     mock_socket_set_response(resp, rlen);
     141              : 
     142              :     MtProtoSession s; Transport t; ApiConfig cfg;
     143            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     144              : 
     145            1 :     HistoryPeer peer = {
     146              :         .kind        = HISTORY_PEER_USER,
     147              :         .peer_id     = 123456789,
     148              :         .access_hash = 0xdeadbeefcafe0000LL,
     149              :     };
     150            1 :     HistoryEntry e[5] = {0}; int n = 0;
     151            1 :     int rc = domain_search_peer(&cfg, &s, &t, &peer, "hello", 5, e, &n);
     152            1 :     ASSERT(rc == 0, "per-peer user search succeeded");
     153            1 :     ASSERT(n == 1,  "one result returned");
     154            1 :     ASSERT(e[0].id == 99, "result id matches");
     155              : }
     156              : 
     157            1 : void run_domain_search_tests(void) {
     158            1 :     RUN_TEST(test_search_global);
     159            1 :     RUN_TEST(test_search_peer);
     160            1 :     RUN_TEST(test_search_rpc_error);
     161            1 :     RUN_TEST(test_search_null_args);
     162            1 :     RUN_TEST(test_search_peer_user_kind);
     163            1 : }
        

Generated by: LCOV version 2.0-1