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 : }
|