Line data Source code
1 : /**
2 : * @file test_write_path.c
3 : * @brief FT-05 — write-path functional tests through the mock server.
4 : *
5 : * Covers the full write surface (US-11..US-13): messages.sendMessage,
6 : * messages.editMessage, messages.deleteMessages / channels.deleteMessages,
7 : * messages.forwardMessages, messages.readHistory / channels.readHistory.
8 : *
9 : * Every test goes through the real rpc_send_encrypted / rpc_recv_encrypted
10 : * path, so the in-process mock server sees exactly the wire bytes the
11 : * client would emit to a real DC.
12 : */
13 :
14 : #include "test_helpers.h"
15 :
16 : #include "mock_socket.h"
17 : #include "mock_tel_server.h"
18 :
19 : #include "api_call.h"
20 : #include "mtproto_session.h"
21 : #include "transport.h"
22 : #include "app/session_store.h"
23 : #include "tl_registry.h"
24 : #include "tl_serial.h"
25 :
26 : #include "domain/write/send.h"
27 : #include "domain/write/edit.h"
28 : #include "domain/write/delete.h"
29 : #include "domain/write/forward.h"
30 : #include "domain/write/read_history.h"
31 :
32 : #include <stdio.h>
33 : #include <stdlib.h>
34 : #include <string.h>
35 : #include <unistd.h>
36 :
37 : /* CRCs the emulator dispatches on. */
38 : #define CRC_messages_sendMessage 0x0d9d75a4U
39 : #define CRC_messages_editMessage 0x48f71778U
40 : #define CRC_messages_deleteMessages 0xe58e95d2U
41 : #define CRC_channels_deleteMessages 0x84c1fd4eU
42 : #define CRC_messages_forwardMessages 0xc661bbc4U
43 : #define CRC_messages_readHistory 0x0e306d3aU
44 : #define CRC_channels_readHistory 0xcc104937U
45 : #define CRC_updateShortSentMessage 0x9015e101U
46 :
47 34 : static void with_tmp_home(const char *tag) {
48 : char tmp[256];
49 34 : snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-write-%s", tag);
50 : char bin[512];
51 34 : snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
52 34 : (void)unlink(bin);
53 34 : setenv("HOME", tmp, 1);
54 34 : }
55 :
56 34 : static void connect_mock(Transport *t) {
57 34 : transport_init(t);
58 34 : ASSERT(transport_connect(t, "127.0.0.1", 443) == 0, "connect");
59 : }
60 :
61 34 : static void init_cfg(ApiConfig *cfg) {
62 34 : api_config_init(cfg);
63 34 : cfg->api_id = 12345;
64 34 : cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
65 34 : }
66 :
67 34 : static void load_session(MtProtoSession *s) {
68 34 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed");
69 34 : mtproto_session_init(s);
70 34 : int dc = 0;
71 34 : ASSERT(session_store_load(s, &dc) == 0, "load");
72 : }
73 :
74 : /* ================================================================ */
75 : /* Reusable reply builders */
76 : /* ================================================================ */
77 :
78 : /* updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int
79 : * pts:int pts_count:int date:int media:flags.9?MessageMedia ...
80 : *
81 : * Minimal construction: flags=0, id=<id>, pts=0, pts_count=0, date=0. */
82 4 : static void reply_update_short_sent(MtRpcContext *ctx, int32_t id) {
83 : TlWriter w;
84 4 : tl_writer_init(&w);
85 4 : tl_write_uint32(&w, CRC_updateShortSentMessage);
86 4 : tl_write_uint32(&w, 0); /* flags */
87 4 : tl_write_int32 (&w, id);
88 4 : tl_write_int32 (&w, 0); /* pts */
89 4 : tl_write_int32 (&w, 0); /* pts_count */
90 4 : tl_write_int32 (&w, 0); /* date */
91 4 : mt_server_reply_result(ctx, w.data, w.len);
92 4 : tl_writer_free(&w);
93 4 : }
94 :
95 : /* updates#74ae4240 updates:Vector<Update> users:Vector<User>
96 : * chats:Vector<Chat> date:int seq:int — empty vectors keep the wire
97 : * minimal. The client does not descend into the vectors for now; it
98 : * only checks the top CRC. */
99 4 : static void reply_updates_empty(MtRpcContext *ctx) {
100 : TlWriter w;
101 4 : tl_writer_init(&w);
102 4 : tl_write_uint32(&w, TL_updates);
103 4 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0); /* updates */
104 4 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0); /* users */
105 4 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0); /* chats */
106 4 : tl_write_int32 (&w, 0); /* date */
107 4 : tl_write_int32 (&w, 0); /* seq */
108 4 : mt_server_reply_result(ctx, w.data, w.len);
109 4 : tl_writer_free(&w);
110 4 : }
111 :
112 : /* messages.affectedMessages#84d19185 pts:int pts_count:int */
113 10 : static void reply_affected_messages(MtRpcContext *ctx) {
114 : TlWriter w;
115 10 : tl_writer_init(&w);
116 10 : tl_write_uint32(&w, TL_messages_affectedMessages);
117 10 : tl_write_int32 (&w, 0); /* pts */
118 10 : tl_write_int32 (&w, 0); /* pts_count */
119 10 : mt_server_reply_result(ctx, w.data, w.len);
120 10 : tl_writer_free(&w);
121 10 : }
122 :
123 2 : static void reply_bool_true(MtRpcContext *ctx) {
124 : TlWriter w;
125 2 : tl_writer_init(&w);
126 2 : tl_write_uint32(&w, TL_boolTrue);
127 2 : mt_server_reply_result(ctx, w.data, w.len);
128 2 : tl_writer_free(&w);
129 2 : }
130 :
131 : /* ================================================================ */
132 : /* Responders */
133 : /* ================================================================ */
134 :
135 4 : static void on_send_message(MtRpcContext *ctx) {
136 4 : reply_update_short_sent(ctx, 555);
137 4 : }
138 :
139 2 : static void on_send_message_peer_invalid(MtRpcContext *ctx) {
140 2 : mt_server_reply_error(ctx, 400, "PEER_ID_INVALID");
141 2 : }
142 :
143 2 : static void on_send_message_flood_wait(MtRpcContext *ctx) {
144 2 : mt_server_reply_error(ctx, 420, "FLOOD_WAIT_30");
145 2 : }
146 :
147 2 : static void on_edit_message(MtRpcContext *ctx) {
148 2 : reply_updates_empty(ctx);
149 2 : }
150 :
151 2 : static void on_edit_not_modified(MtRpcContext *ctx) {
152 2 : mt_server_reply_error(ctx, 400, "MESSAGE_NOT_MODIFIED");
153 2 : }
154 :
155 2 : static void on_delete_messages(MtRpcContext *ctx) {
156 2 : reply_affected_messages(ctx);
157 2 : }
158 :
159 2 : static void on_channels_delete(MtRpcContext *ctx) {
160 2 : reply_affected_messages(ctx);
161 2 : }
162 :
163 : /* Responder that asserts flags.0 == 0 (no revoke) and returns ok. */
164 2 : static void on_delete_no_revoke(MtRpcContext *ctx) {
165 : /* req_body layout: [CRC:4][flags:4][vector...] */
166 2 : ASSERT(ctx->req_body_len >= 8, "req_body large enough for flags");
167 2 : uint32_t flags = 0;
168 2 : memcpy(&flags, ctx->req_body + 4, 4);
169 2 : ASSERT((flags & 1u) == 0u, "flags.0 must be clear (no revoke)");
170 2 : reply_affected_messages(ctx);
171 : }
172 :
173 : /* Responder that asserts flags.0 == 1 (revoke set) and returns ok. */
174 2 : static void on_delete_with_revoke(MtRpcContext *ctx) {
175 2 : ASSERT(ctx->req_body_len >= 8, "req_body large enough for flags");
176 2 : uint32_t flags = 0;
177 2 : memcpy(&flags, ctx->req_body + 4, 4);
178 2 : ASSERT((flags & 1u) == 1u, "flags.0 must be set (revoke)");
179 2 : reply_affected_messages(ctx);
180 : }
181 :
182 2 : static void on_edit_message_id_invalid(MtRpcContext *ctx) {
183 2 : mt_server_reply_error(ctx, 400, "MESSAGE_ID_INVALID");
184 2 : }
185 :
186 2 : static void on_edit_author_required(MtRpcContext *ctx) {
187 2 : mt_server_reply_error(ctx, 403, "MESSAGE_AUTHOR_REQUIRED");
188 2 : }
189 :
190 2 : static void on_delete_peer_id_invalid(MtRpcContext *ctx) {
191 2 : mt_server_reply_error(ctx, 400, "PEER_ID_INVALID");
192 2 : }
193 :
194 2 : static void on_forward_messages(MtRpcContext *ctx) {
195 2 : reply_updates_empty(ctx);
196 2 : }
197 :
198 2 : static void on_read_history(MtRpcContext *ctx) {
199 2 : reply_affected_messages(ctx);
200 2 : }
201 :
202 2 : static void on_channels_read_history(MtRpcContext *ctx) {
203 2 : reply_bool_true(ctx);
204 2 : }
205 :
206 : /* ================================================================ */
207 : /* Tests */
208 : /* ================================================================ */
209 :
210 2 : static void test_send_message_happy(void) {
211 2 : with_tmp_home("send-ok");
212 2 : mt_server_init(); mt_server_reset();
213 2 : MtProtoSession s; load_session(&s);
214 2 : mt_server_expect(CRC_messages_sendMessage, on_send_message, NULL);
215 :
216 2 : ApiConfig cfg; init_cfg(&cfg);
217 2 : Transport t; connect_mock(&t);
218 :
219 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
220 2 : int32_t mid = 0;
221 2 : RpcError err = {0};
222 2 : ASSERT(domain_send_message(&cfg, &s, &t, &self,
223 : "hello from tg-cli", &mid, &err) == 0,
224 : "sendMessage succeeds");
225 2 : ASSERT(mid == 555, "message id echoed from updateShortSentMessage");
226 :
227 2 : transport_close(&t);
228 2 : mt_server_reset();
229 : }
230 :
231 2 : static void test_send_message_reply(void) {
232 2 : with_tmp_home("send-reply");
233 2 : mt_server_init(); mt_server_reset();
234 2 : MtProtoSession s; load_session(&s);
235 2 : mt_server_expect(CRC_messages_sendMessage, on_send_message, NULL);
236 :
237 2 : ApiConfig cfg; init_cfg(&cfg);
238 2 : Transport t; connect_mock(&t);
239 :
240 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
241 2 : int32_t mid = 0;
242 2 : RpcError err = {0};
243 2 : ASSERT(domain_send_message_reply(&cfg, &s, &t, &self,
244 : "thread reply", 100, &mid, &err) == 0,
245 : "sendMessage w/ reply succeeds");
246 2 : ASSERT(mid == 555, "id still echoed");
247 :
248 2 : transport_close(&t);
249 2 : mt_server_reset();
250 : }
251 :
252 2 : static void test_send_message_empty_rejected(void) {
253 2 : with_tmp_home("send-empty");
254 2 : mt_server_init(); mt_server_reset();
255 2 : MtProtoSession s; load_session(&s);
256 : /* No handler registered — the call must be rejected before reaching
257 : * the network. */
258 :
259 2 : ApiConfig cfg; init_cfg(&cfg);
260 2 : Transport t; connect_mock(&t);
261 :
262 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
263 2 : int32_t mid = 0;
264 2 : RpcError err = {0};
265 2 : ASSERT(domain_send_message(&cfg, &s, &t, &self, "", &mid, &err) == -1,
266 : "empty message rejected client-side");
267 2 : ASSERT(mt_server_rpc_call_count() == 0,
268 : "no RPC dispatched for empty message");
269 :
270 2 : transport_close(&t);
271 2 : mt_server_reset();
272 : }
273 :
274 2 : static void test_send_message_rpc_error(void) {
275 2 : with_tmp_home("send-err");
276 2 : mt_server_init(); mt_server_reset();
277 2 : MtProtoSession s; load_session(&s);
278 2 : mt_server_expect(CRC_messages_sendMessage,
279 : on_send_message_peer_invalid, NULL);
280 :
281 2 : ApiConfig cfg; init_cfg(&cfg);
282 2 : Transport t; connect_mock(&t);
283 :
284 2 : HistoryPeer bogus = {
285 : .kind = HISTORY_PEER_USER, .peer_id = 9, .access_hash = 0
286 : };
287 2 : int32_t mid = 0;
288 2 : RpcError err = {0};
289 2 : ASSERT(domain_send_message(&cfg, &s, &t, &bogus, "oops", &mid, &err) == -1,
290 : "sendMessage -1 on PEER_ID_INVALID");
291 2 : ASSERT(err.error_code == 400, "400");
292 2 : ASSERT(strcmp(err.error_msg, "PEER_ID_INVALID") == 0,
293 : "PEER_ID_INVALID propagated");
294 :
295 2 : transport_close(&t);
296 2 : mt_server_reset();
297 : }
298 :
299 2 : static void test_send_message_flood_wait(void) {
300 2 : with_tmp_home("send-flood");
301 2 : mt_server_init(); mt_server_reset();
302 2 : MtProtoSession s; load_session(&s);
303 2 : mt_server_expect(CRC_messages_sendMessage, on_send_message_flood_wait, NULL);
304 :
305 2 : ApiConfig cfg; init_cfg(&cfg);
306 2 : Transport t; connect_mock(&t);
307 :
308 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
309 2 : int32_t mid = 0;
310 2 : RpcError err = {0};
311 2 : int rc = domain_send_message(&cfg, &s, &t, &self, "hi", &mid, &err);
312 2 : ASSERT(rc == -1, "FLOOD_WAIT_30 must return -1");
313 2 : ASSERT(err.error_code == 420, "error_code == 420");
314 2 : ASSERT(strcmp(err.error_msg, "FLOOD_WAIT_30") == 0,
315 : "error_msg is FLOOD_WAIT_30");
316 2 : ASSERT(err.flood_wait_secs == 30, "flood_wait_secs parsed as 30");
317 : /* Verify no auto-retry: exactly one RPC call dispatched. */
318 2 : ASSERT(mt_server_rpc_call_count() == 1,
319 : "no auto-retry: exactly 1 RPC call");
320 :
321 2 : transport_close(&t);
322 2 : mt_server_reset();
323 : }
324 :
325 2 : static void test_edit_message_happy(void) {
326 2 : with_tmp_home("edit-ok");
327 2 : mt_server_init(); mt_server_reset();
328 2 : MtProtoSession s; load_session(&s);
329 2 : mt_server_expect(CRC_messages_editMessage, on_edit_message, NULL);
330 :
331 2 : ApiConfig cfg; init_cfg(&cfg);
332 2 : Transport t; connect_mock(&t);
333 :
334 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
335 2 : RpcError err = {0};
336 2 : ASSERT(domain_edit_message(&cfg, &s, &t, &self, 100,
337 : "edited text", &err) == 0,
338 : "editMessage succeeds");
339 :
340 2 : transport_close(&t);
341 2 : mt_server_reset();
342 : }
343 :
344 2 : static void test_edit_message_not_modified(void) {
345 2 : with_tmp_home("edit-nm");
346 2 : mt_server_init(); mt_server_reset();
347 2 : MtProtoSession s; load_session(&s);
348 2 : mt_server_expect(CRC_messages_editMessage, on_edit_not_modified, NULL);
349 :
350 2 : ApiConfig cfg; init_cfg(&cfg);
351 2 : Transport t; connect_mock(&t);
352 :
353 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
354 2 : RpcError err = {0};
355 2 : ASSERT(domain_edit_message(&cfg, &s, &t, &self, 100,
356 : "same text", &err) == -1,
357 : "editMessage -1 on MESSAGE_NOT_MODIFIED");
358 2 : ASSERT(strcmp(err.error_msg, "MESSAGE_NOT_MODIFIED") == 0, "msg");
359 :
360 2 : transport_close(&t);
361 2 : mt_server_reset();
362 : }
363 :
364 2 : static void test_delete_messages_user(void) {
365 2 : with_tmp_home("del-user");
366 2 : mt_server_init(); mt_server_reset();
367 2 : MtProtoSession s; load_session(&s);
368 2 : mt_server_expect(CRC_messages_deleteMessages, on_delete_messages, NULL);
369 :
370 2 : ApiConfig cfg; init_cfg(&cfg);
371 2 : Transport t; connect_mock(&t);
372 :
373 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
374 2 : int32_t ids[] = {1, 2, 3};
375 2 : RpcError err = {0};
376 2 : ASSERT(domain_delete_messages(&cfg, &s, &t, &self, ids, 3,
377 : /*revoke=*/1, &err) == 0,
378 : "deleteMessages (user/chat) ok");
379 :
380 2 : transport_close(&t);
381 2 : mt_server_reset();
382 : }
383 :
384 2 : static void test_delete_messages_channel(void) {
385 2 : with_tmp_home("del-chan");
386 2 : mt_server_init(); mt_server_reset();
387 2 : MtProtoSession s; load_session(&s);
388 2 : mt_server_expect(CRC_channels_deleteMessages, on_channels_delete, NULL);
389 :
390 2 : ApiConfig cfg; init_cfg(&cfg);
391 2 : Transport t; connect_mock(&t);
392 :
393 2 : HistoryPeer chan = {
394 : .kind = HISTORY_PEER_CHANNEL,
395 : .peer_id = 1234567,
396 : .access_hash = 0x1111222233334444LL
397 : };
398 2 : int32_t ids[] = {42};
399 2 : RpcError err = {0};
400 2 : ASSERT(domain_delete_messages(&cfg, &s, &t, &chan, ids, 1,
401 : /*revoke=*/0, &err) == 0,
402 : "deleteMessages (channel) ok");
403 :
404 2 : transport_close(&t);
405 2 : mt_server_reset();
406 : }
407 :
408 2 : static void test_delete_messages_no_revoke(void) {
409 2 : with_tmp_home("del-no-revoke");
410 2 : mt_server_init(); mt_server_reset();
411 2 : MtProtoSession s; load_session(&s);
412 2 : mt_server_expect(CRC_messages_deleteMessages, on_delete_no_revoke, NULL);
413 :
414 2 : ApiConfig cfg; init_cfg(&cfg);
415 2 : Transport t; connect_mock(&t);
416 :
417 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
418 2 : int32_t ids[] = {10};
419 2 : RpcError err = {0};
420 2 : ASSERT(domain_delete_messages(&cfg, &s, &t, &self, ids, 1,
421 : /*revoke=*/0, &err) == 0,
422 : "deleteMessages (no revoke) ok");
423 :
424 2 : transport_close(&t);
425 2 : mt_server_reset();
426 : }
427 :
428 2 : static void test_delete_messages_with_revoke(void) {
429 2 : with_tmp_home("del-revoke");
430 2 : mt_server_init(); mt_server_reset();
431 2 : MtProtoSession s; load_session(&s);
432 2 : mt_server_expect(CRC_messages_deleteMessages, on_delete_with_revoke, NULL);
433 :
434 2 : ApiConfig cfg; init_cfg(&cfg);
435 2 : Transport t; connect_mock(&t);
436 :
437 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
438 2 : int32_t ids[] = {20};
439 2 : RpcError err = {0};
440 2 : ASSERT(domain_delete_messages(&cfg, &s, &t, &self, ids, 1,
441 : /*revoke=*/1, &err) == 0,
442 : "deleteMessages (with --revoke) ok");
443 :
444 2 : transport_close(&t);
445 2 : mt_server_reset();
446 : }
447 :
448 2 : static void test_edit_message_id_invalid(void) {
449 2 : with_tmp_home("edit-mid-inv");
450 2 : mt_server_init(); mt_server_reset();
451 2 : MtProtoSession s; load_session(&s);
452 2 : mt_server_expect(CRC_messages_editMessage, on_edit_message_id_invalid, NULL);
453 :
454 2 : ApiConfig cfg; init_cfg(&cfg);
455 2 : Transport t; connect_mock(&t);
456 :
457 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
458 2 : RpcError err = {0};
459 2 : ASSERT(domain_edit_message(&cfg, &s, &t, &self, 9999,
460 : "new text", &err) == -1,
461 : "editMessage -1 on MESSAGE_ID_INVALID");
462 2 : ASSERT(err.error_code == 400, "error_code == 400");
463 2 : ASSERT(strcmp(err.error_msg, "MESSAGE_ID_INVALID") == 0,
464 : "MESSAGE_ID_INVALID propagated");
465 :
466 2 : transport_close(&t);
467 2 : mt_server_reset();
468 : }
469 :
470 2 : static void test_edit_message_author_required(void) {
471 2 : with_tmp_home("edit-auth-req");
472 2 : mt_server_init(); mt_server_reset();
473 2 : MtProtoSession s; load_session(&s);
474 2 : mt_server_expect(CRC_messages_editMessage, on_edit_author_required, NULL);
475 :
476 2 : ApiConfig cfg; init_cfg(&cfg);
477 2 : Transport t; connect_mock(&t);
478 :
479 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
480 2 : RpcError err = {0};
481 2 : ASSERT(domain_edit_message(&cfg, &s, &t, &self, 100,
482 : "not my msg", &err) == -1,
483 : "editMessage -1 on MESSAGE_AUTHOR_REQUIRED");
484 2 : ASSERT(err.error_code == 403, "error_code == 403");
485 2 : ASSERT(strcmp(err.error_msg, "MESSAGE_AUTHOR_REQUIRED") == 0,
486 : "MESSAGE_AUTHOR_REQUIRED propagated");
487 :
488 2 : transport_close(&t);
489 2 : mt_server_reset();
490 : }
491 :
492 2 : static void test_delete_peer_id_invalid(void) {
493 2 : with_tmp_home("del-peer-inv");
494 2 : mt_server_init(); mt_server_reset();
495 2 : MtProtoSession s; load_session(&s);
496 2 : mt_server_expect(CRC_messages_deleteMessages, on_delete_peer_id_invalid, NULL);
497 :
498 2 : ApiConfig cfg; init_cfg(&cfg);
499 2 : Transport t; connect_mock(&t);
500 :
501 2 : HistoryPeer bogus = {
502 : .kind = HISTORY_PEER_USER, .peer_id = 0, .access_hash = 0
503 : };
504 2 : int32_t ids[] = {1};
505 2 : RpcError err = {0};
506 2 : ASSERT(domain_delete_messages(&cfg, &s, &t, &bogus, ids, 1,
507 : /*revoke=*/0, &err) == -1,
508 : "deleteMessages -1 on PEER_ID_INVALID");
509 2 : ASSERT(err.error_code == 400, "error_code == 400");
510 2 : ASSERT(strcmp(err.error_msg, "PEER_ID_INVALID") == 0,
511 : "PEER_ID_INVALID propagated");
512 :
513 2 : transport_close(&t);
514 2 : mt_server_reset();
515 : }
516 :
517 2 : static void test_forward_messages(void) {
518 2 : with_tmp_home("fwd");
519 2 : mt_server_init(); mt_server_reset();
520 2 : MtProtoSession s; load_session(&s);
521 2 : mt_server_expect(CRC_messages_forwardMessages, on_forward_messages, NULL);
522 :
523 2 : ApiConfig cfg; init_cfg(&cfg);
524 2 : Transport t; connect_mock(&t);
525 :
526 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
527 2 : HistoryPeer other = {
528 : .kind = HISTORY_PEER_USER,
529 : .peer_id = 555,
530 : .access_hash = 0xDEAD
531 : };
532 2 : int32_t ids[] = {10, 20};
533 2 : RpcError err = {0};
534 2 : ASSERT(domain_forward_messages(&cfg, &s, &t, &self, &other, ids, 2,
535 : &err) == 0,
536 : "forwardMessages ok");
537 :
538 2 : transport_close(&t);
539 2 : mt_server_reset();
540 : }
541 :
542 2 : static void test_mark_read_user(void) {
543 2 : with_tmp_home("read-user");
544 2 : mt_server_init(); mt_server_reset();
545 2 : MtProtoSession s; load_session(&s);
546 2 : mt_server_expect(CRC_messages_readHistory, on_read_history, NULL);
547 :
548 2 : ApiConfig cfg; init_cfg(&cfg);
549 2 : Transport t; connect_mock(&t);
550 :
551 2 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
552 2 : RpcError err = {0};
553 2 : ASSERT(domain_mark_read(&cfg, &s, &t, &self, 100, &err) == 0,
554 : "mark_read (user/chat) ok");
555 :
556 2 : transport_close(&t);
557 2 : mt_server_reset();
558 : }
559 :
560 2 : static void test_mark_read_channel(void) {
561 2 : with_tmp_home("read-chan");
562 2 : mt_server_init(); mt_server_reset();
563 2 : MtProtoSession s; load_session(&s);
564 2 : mt_server_expect(CRC_channels_readHistory, on_channels_read_history, NULL);
565 :
566 2 : ApiConfig cfg; init_cfg(&cfg);
567 2 : Transport t; connect_mock(&t);
568 :
569 2 : HistoryPeer chan = {
570 : .kind = HISTORY_PEER_CHANNEL,
571 : .peer_id = 99,
572 : .access_hash = 0xABCD
573 : };
574 2 : RpcError err = {0};
575 2 : ASSERT(domain_mark_read(&cfg, &s, &t, &chan, 500, &err) == 0,
576 : "mark_read (channel) ok");
577 :
578 2 : transport_close(&t);
579 2 : mt_server_reset();
580 : }
581 :
582 2 : void run_write_path_tests(void) {
583 2 : RUN_TEST(test_send_message_happy);
584 2 : RUN_TEST(test_send_message_reply);
585 2 : RUN_TEST(test_send_message_empty_rejected);
586 2 : RUN_TEST(test_send_message_rpc_error);
587 2 : RUN_TEST(test_send_message_flood_wait);
588 2 : RUN_TEST(test_edit_message_happy);
589 2 : RUN_TEST(test_edit_message_not_modified);
590 2 : RUN_TEST(test_edit_message_id_invalid);
591 2 : RUN_TEST(test_edit_message_author_required);
592 2 : RUN_TEST(test_delete_peer_id_invalid);
593 2 : RUN_TEST(test_delete_messages_user);
594 2 : RUN_TEST(test_delete_messages_channel);
595 2 : RUN_TEST(test_delete_messages_no_revoke);
596 2 : RUN_TEST(test_delete_messages_with_revoke);
597 2 : RUN_TEST(test_forward_messages);
598 2 : RUN_TEST(test_mark_read_user);
599 2 : RUN_TEST(test_mark_read_channel);
600 2 : }
|