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

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

Generated by: LCOV version 2.0-1