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

            Line data    Source code
       1              : /**
       2              :  * @file test_history_rich_metadata.c
       3              :  * @brief TEST-79 — functional coverage for rich message metadata.
       4              :  *
       5              :  * US-28 identifies five Message flag branches in
       6              :  * `src/domain/read/history.c` that the existing read-path suite never
       7              :  * feeds: fwd_from (flags.2), via_bot_id (flags.11), reply_to (flags.3),
       8              :  * via_business_bot_id (flags2.0), and saved_peer_id (flags.28). Each
       9              :  * branch has a skipper in tl_skip.c that must execute for the parser
      10              :  * to surface the message body rather than drop the row.
      11              :  *
      12              :  * These scenarios craft real messages.Messages payloads carrying each
      13              :  * flag combination, drive the production domain_get_history() through
      14              :  * the in-process mock server, and assert that id / date / text still
      15              :  * arrive intact. Full "[fwd from @channel]" rendering is US-28 scope;
      16              :  * this suite validates the parse-through contract that unlocks it.
      17              :  */
      18              : 
      19              : #include "test_helpers.h"
      20              : 
      21              : #include "mock_socket.h"
      22              : #include "mock_tel_server.h"
      23              : 
      24              : #include "api_call.h"
      25              : #include "mtproto_session.h"
      26              : #include "transport.h"
      27              : #include "app/session_store.h"
      28              : #include "tl_registry.h"
      29              : #include "tl_serial.h"
      30              : 
      31              : #include "domain/read/history.h"
      32              : 
      33              : #include <stdio.h>
      34              : #include <stdlib.h>
      35              : #include <string.h>
      36              : #include <unistd.h>
      37              : 
      38              : /* ---- CRCs not re-exposed from public headers ---- */
      39              : #define CRC_messages_getHistory 0x4423e6c5U
      40              : #define CRC_messageFwdHeader    0x4e4df4bbU
      41              : #define CRC_messageReplyHeader  0xafbc09dbU
      42              : 
      43              : /* Message flag bits tested here. */
      44              : #define MSG_FLAG_FWD_FROM       (1u <<  2)
      45              : #define MSG_FLAG_REPLY_TO       (1u <<  3)
      46              : #define MSG_FLAG_FROM_ID        (1u <<  8)
      47              : #define MSG_FLAG_VIA_BOT        (1u << 11)
      48              : #define MSG_FLAG_SAVED_PEER     (1u << 28)
      49              : #define MSG2_FLAG_VIA_BIZ_BOT   (1u <<  0)
      50              : 
      51              : /* Fwd-header flag bits used below. */
      52              : #define FWD_HAS_FROM_ID         (1u << 0)
      53              : #define FWD_HAS_FROM_NAME       (1u << 5)
      54              : 
      55              : /* Reply-header flag bits used below. */
      56              : #define REPLY_HAS_MSG_ID        (1u << 4)
      57              : 
      58              : /* ---- boilerplate ---- */
      59              : 
      60           16 : static void with_tmp_home(const char *tag) {
      61              :     char tmp[256];
      62           16 :     snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-hist-rich-%s", tag);
      63              :     char bin[512];
      64           16 :     snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
      65           16 :     (void)unlink(bin);
      66           16 :     setenv("HOME", tmp, 1);
      67           16 : }
      68              : 
      69           16 : static void connect_mock(Transport *t) {
      70           16 :     transport_init(t);
      71           16 :     ASSERT(transport_connect(t, "127.0.0.1", 443) == 0, "connect");
      72              : }
      73              : 
      74           16 : static void init_cfg(ApiConfig *cfg) {
      75           16 :     api_config_init(cfg);
      76           16 :     cfg->api_id = 12345;
      77           16 :     cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
      78           16 : }
      79              : 
      80           16 : static void load_session(MtProtoSession *s) {
      81           16 :     ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed");
      82           16 :     mtproto_session_init(s);
      83           16 :     int dc = 0;
      84           16 :     ASSERT(session_store_load(s, &dc) == 0, "load session");
      85              : }
      86              : 
      87              : /* Envelope: messages.messages { messages: Vector<Message>{1}, chats, users }
      88              :  * with the caller providing the inner message bytes (starting at TL_message). */
      89           16 : static void wrap_messages_messages(TlWriter *w, const uint8_t *msg_bytes,
      90              :                                     size_t msg_len) {
      91           16 :     tl_write_uint32(w, TL_messages_messages);
      92           16 :     tl_write_uint32(w, TL_vector);
      93           16 :     tl_write_uint32(w, 1);
      94           16 :     tl_write_raw(w, msg_bytes, msg_len);
      95           16 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 0); /* chats */
      96           16 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 0); /* users */
      97           16 : }
      98              : 
      99              : /* ================================================================ */
     100              : /* Responders                                                       */
     101              : /* ================================================================ */
     102              : 
     103              : /* Shared pre-text layout (no from_id unless FROM_ID flag set):
     104              :  *   TL_message | flags | flags2 | id(i32)
     105              :  *   [from_id:Peer  if flags.8]
     106              :  *   peer_id:Peer (peerUser 1)
     107              :  *   [saved_peer_id:Peer  if flags.28]
     108              :  *   [fwd_from:MessageFwdHeader  if flags.2]
     109              :  *   [via_bot_id:i64  if flags.11]
     110              :  *   [via_business_bot_id:i64  if flags2.0]
     111              :  *   [reply_to:MessageReplyHeader  if flags.3]
     112              :  *   date(i32)  message:string
     113              :  */
     114              : 
     115              : /* Test 1 — fwd_from with a channel peer (from_id variant). */
     116            2 : static void on_history_fwd_from_channel(MtRpcContext *ctx) {
     117            2 :     TlWriter inner; tl_writer_init(&inner);
     118            2 :     tl_write_uint32(&inner, TL_message);
     119            2 :     tl_write_uint32(&inner, MSG_FLAG_FWD_FROM);
     120            2 :     tl_write_uint32(&inner, 0);                /* flags2 */
     121            2 :     tl_write_int32 (&inner, 10347);            /* id */
     122            2 :     tl_write_uint32(&inner, TL_peerUser);      /* peer_id */
     123            2 :     tl_write_int64 (&inner, 1LL);
     124              :     /* messageFwdHeader flags: from_id=peerChannel */
     125            2 :     tl_write_uint32(&inner, CRC_messageFwdHeader);
     126            2 :     tl_write_uint32(&inner, FWD_HAS_FROM_ID);
     127            2 :     tl_write_uint32(&inner, TL_peerChannel);   /* from_id */
     128            2 :     tl_write_int64 (&inner, 12345678LL);
     129            2 :     tl_write_int32 (&inner, 1700000123);       /* fwd date */
     130            2 :     tl_write_int32 (&inner, 1700000200);       /* outer date */
     131            2 :     tl_write_string(&inner, "fresh news");     /* message */
     132              : 
     133            2 :     TlWriter w; tl_writer_init(&w);
     134            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     135            2 :     mt_server_reply_result(ctx, w.data, w.len);
     136            2 :     tl_writer_free(&w);
     137            2 :     tl_writer_free(&inner);
     138            2 : }
     139              : 
     140              : /* Test 2 — fwd_from with a hidden-user from_name (string, not peer). */
     141            2 : static void on_history_fwd_from_hidden(MtRpcContext *ctx) {
     142            2 :     TlWriter inner; tl_writer_init(&inner);
     143            2 :     tl_write_uint32(&inner, TL_message);
     144            2 :     tl_write_uint32(&inner, MSG_FLAG_FWD_FROM);
     145            2 :     tl_write_uint32(&inner, 0);
     146            2 :     tl_write_int32 (&inner, 10348);
     147            2 :     tl_write_uint32(&inner, TL_peerUser);
     148            2 :     tl_write_int64 (&inner, 1LL);
     149              :     /* fwd header with only from_name (flag 5). */
     150            2 :     tl_write_uint32(&inner, CRC_messageFwdHeader);
     151            2 :     tl_write_uint32(&inner, FWD_HAS_FROM_NAME);
     152            2 :     tl_write_string(&inner, "HiddenSender");   /* from_name */
     153            2 :     tl_write_int32 (&inner, 1700000500);       /* fwd date */
     154            2 :     tl_write_int32 (&inner, 1700000600);       /* outer date */
     155            2 :     tl_write_string(&inner, "hidden fwd body");
     156              : 
     157            2 :     TlWriter w; tl_writer_init(&w);
     158            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     159            2 :     mt_server_reply_result(ctx, w.data, w.len);
     160            2 :     tl_writer_free(&w);
     161            2 :     tl_writer_free(&inner);
     162            2 : }
     163              : 
     164              : /* Test 3 — reply_to referencing msg_id=12345. */
     165            2 : static void on_history_reply_to(MtRpcContext *ctx) {
     166            2 :     TlWriter inner; tl_writer_init(&inner);
     167            2 :     tl_write_uint32(&inner, TL_message);
     168            2 :     tl_write_uint32(&inner, MSG_FLAG_REPLY_TO);
     169            2 :     tl_write_uint32(&inner, 0);
     170            2 :     tl_write_int32 (&inner, 12346);
     171            2 :     tl_write_uint32(&inner, TL_peerUser);
     172            2 :     tl_write_int64 (&inner, 1LL);
     173              :     /* messageReplyHeader with reply_to_msg_id. */
     174            2 :     tl_write_uint32(&inner, CRC_messageReplyHeader);
     175            2 :     tl_write_uint32(&inner, REPLY_HAS_MSG_ID);
     176            2 :     tl_write_int32 (&inner, 12345);            /* reply_to_msg_id */
     177            2 :     tl_write_int32 (&inner, 1700000800);       /* date */
     178            2 :     tl_write_string(&inner, "yes");
     179              : 
     180            2 :     TlWriter w; tl_writer_init(&w);
     181            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     182            2 :     mt_server_reply_result(ctx, w.data, w.len);
     183            2 :     tl_writer_free(&w);
     184            2 :     tl_writer_free(&inner);
     185            2 : }
     186              : 
     187              : /* Test 4 — via_bot_id (e.g. @gif). */
     188            2 : static void on_history_via_bot(MtRpcContext *ctx) {
     189            2 :     TlWriter inner; tl_writer_init(&inner);
     190            2 :     tl_write_uint32(&inner, TL_message);
     191            2 :     tl_write_uint32(&inner, MSG_FLAG_VIA_BOT);
     192            2 :     tl_write_uint32(&inner, 0);
     193            2 :     tl_write_int32 (&inner, 20001);
     194            2 :     tl_write_uint32(&inner, TL_peerUser);
     195            2 :     tl_write_int64 (&inner, 1LL);
     196            2 :     tl_write_int64 (&inner, 7777001LL);        /* via_bot_id */
     197            2 :     tl_write_int32 (&inner, 1700001000);       /* date */
     198            2 :     tl_write_string(&inner, "<gif>");
     199              : 
     200            2 :     TlWriter w; tl_writer_init(&w);
     201            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     202            2 :     mt_server_reply_result(ctx, w.data, w.len);
     203            2 :     tl_writer_free(&w);
     204            2 :     tl_writer_free(&inner);
     205            2 : }
     206              : 
     207              : /* Test 5 — via_business_bot_id (flags2 bit 0). */
     208            2 : static void on_history_via_business_bot(MtRpcContext *ctx) {
     209            2 :     TlWriter inner; tl_writer_init(&inner);
     210            2 :     tl_write_uint32(&inner, TL_message);
     211            2 :     tl_write_uint32(&inner, 0);                /* flags */
     212            2 :     tl_write_uint32(&inner, MSG2_FLAG_VIA_BIZ_BOT); /* flags2 */
     213            2 :     tl_write_int32 (&inner, 20101);
     214            2 :     tl_write_uint32(&inner, TL_peerUser);
     215            2 :     tl_write_int64 (&inner, 1LL);
     216            2 :     tl_write_int64 (&inner, 8888001LL);        /* via_business_bot_id */
     217            2 :     tl_write_int32 (&inner, 1700001100);
     218            2 :     tl_write_string(&inner, "auto reply");
     219              : 
     220            2 :     TlWriter w; tl_writer_init(&w);
     221            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     222            2 :     mt_server_reply_result(ctx, w.data, w.len);
     223            2 :     tl_writer_free(&w);
     224            2 :     tl_writer_free(&inner);
     225            2 : }
     226              : 
     227              : /* Test 6 — saved_peer_id (flags.28). */
     228            2 : static void on_history_saved_peer(MtRpcContext *ctx) {
     229            2 :     TlWriter inner; tl_writer_init(&inner);
     230            2 :     tl_write_uint32(&inner, TL_message);
     231            2 :     tl_write_uint32(&inner, MSG_FLAG_SAVED_PEER);
     232            2 :     tl_write_uint32(&inner, 0);
     233            2 :     tl_write_int32 (&inner, 30001);
     234            2 :     tl_write_uint32(&inner, TL_peerUser);
     235            2 :     tl_write_int64 (&inner, 1LL);
     236            2 :     tl_write_uint32(&inner, TL_peerUser);      /* saved_peer_id */
     237            2 :     tl_write_int64 (&inner, 42LL);
     238            2 :     tl_write_int32 (&inner, 1700001200);
     239            2 :     tl_write_string(&inner, "topic-A msg");
     240              : 
     241            2 :     TlWriter w; tl_writer_init(&w);
     242            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     243            2 :     mt_server_reply_result(ctx, w.data, w.len);
     244            2 :     tl_writer_free(&w);
     245            2 :     tl_writer_free(&inner);
     246            2 : }
     247              : 
     248              : /* Test 7 — reply_to AND via_bot on the same message. */
     249            2 : static void on_history_reply_and_via_bot(MtRpcContext *ctx) {
     250            2 :     TlWriter inner; tl_writer_init(&inner);
     251            2 :     tl_write_uint32(&inner, TL_message);
     252            2 :     tl_write_uint32(&inner, MSG_FLAG_REPLY_TO | MSG_FLAG_VIA_BOT);
     253            2 :     tl_write_uint32(&inner, 0);
     254            2 :     tl_write_int32 (&inner, 40001);
     255            2 :     tl_write_uint32(&inner, TL_peerUser);
     256            2 :     tl_write_int64 (&inner, 1LL);
     257              :     /* via_bot_id comes BEFORE reply_to in schema order. */
     258            2 :     tl_write_int64 (&inner, 9990001LL);
     259            2 :     tl_write_uint32(&inner, CRC_messageReplyHeader);
     260            2 :     tl_write_uint32(&inner, REPLY_HAS_MSG_ID);
     261            2 :     tl_write_int32 (&inner, 39000);
     262            2 :     tl_write_int32 (&inner, 1700001300);
     263            2 :     tl_write_string(&inner, "combo reply");
     264              : 
     265            2 :     TlWriter w; tl_writer_init(&w);
     266            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     267            2 :     mt_server_reply_result(ctx, w.data, w.len);
     268            2 :     tl_writer_free(&w);
     269            2 :     tl_writer_free(&inner);
     270            2 : }
     271              : 
     272              : /* Test 8 — via_bot_id set but id not resolvable (no users vector entry).
     273              :  * Parser must still land the line with text intact, not drop it. */
     274            2 : static void on_history_via_bot_unresolvable(MtRpcContext *ctx) {
     275            2 :     TlWriter inner; tl_writer_init(&inner);
     276            2 :     tl_write_uint32(&inner, TL_message);
     277            2 :     tl_write_uint32(&inner, MSG_FLAG_VIA_BOT);
     278            2 :     tl_write_uint32(&inner, 0);
     279            2 :     tl_write_int32 (&inner, 50001);
     280            2 :     tl_write_uint32(&inner, TL_peerUser);
     281            2 :     tl_write_int64 (&inner, 1LL);
     282            2 :     tl_write_int64 (&inner, 424242LL);         /* unknown bot id */
     283            2 :     tl_write_int32 (&inner, 1700001400);
     284            2 :     tl_write_string(&inner, "orphan bot msg");
     285              : 
     286            2 :     TlWriter w; tl_writer_init(&w);
     287            2 :     wrap_messages_messages(&w, inner.data, inner.len);
     288            2 :     mt_server_reply_result(ctx, w.data, w.len);
     289            2 :     tl_writer_free(&w);
     290            2 :     tl_writer_free(&inner);
     291            2 : }
     292              : 
     293              : /* ================================================================ */
     294              : /* Tests                                                            */
     295              : /* ================================================================ */
     296              : 
     297            2 : static void test_forwarded_from_channel_labelled(void) {
     298            2 :     with_tmp_home("fwd-chan");
     299            2 :     mt_server_init(); mt_server_reset();
     300            2 :     MtProtoSession s; load_session(&s);
     301            2 :     mt_server_expect(CRC_messages_getHistory, on_history_fwd_from_channel, NULL);
     302              : 
     303            2 :     ApiConfig cfg; init_cfg(&cfg);
     304            2 :     Transport t; connect_mock(&t);
     305              : 
     306              :     HistoryEntry rows[4];
     307            2 :     int n = 0;
     308            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     309              :            "get_history with fwd_from channel succeeds");
     310            2 :     ASSERT(n == 1, "one fwd message parsed");
     311            2 :     ASSERT(rows[0].id == 10347, "id preserved past fwd header");
     312            2 :     ASSERT(rows[0].date == 1700000200, "outer date preserved");
     313            2 :     ASSERT(strcmp(rows[0].text, "fresh news") == 0,
     314              :            "text after fwd_from skipped correctly");
     315            2 :     ASSERT(rows[0].complex == 0, "not flagged complex — fwd header fully skipped");
     316              : 
     317            2 :     transport_close(&t);
     318            2 :     mt_server_reset();
     319              : }
     320              : 
     321            2 : static void test_forwarded_from_hidden_user_labelled(void) {
     322            2 :     with_tmp_home("fwd-hidden");
     323            2 :     mt_server_init(); mt_server_reset();
     324            2 :     MtProtoSession s; load_session(&s);
     325            2 :     mt_server_expect(CRC_messages_getHistory, on_history_fwd_from_hidden, NULL);
     326              : 
     327            2 :     ApiConfig cfg; init_cfg(&cfg);
     328            2 :     Transport t; connect_mock(&t);
     329              : 
     330              :     HistoryEntry rows[4];
     331            2 :     int n = 0;
     332            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     333              :            "get_history with fwd from_name succeeds");
     334            2 :     ASSERT(n == 1, "hidden-sender fwd preserved");
     335            2 :     ASSERT(rows[0].id == 10348, "id preserved");
     336            2 :     ASSERT(strcmp(rows[0].text, "hidden fwd body") == 0,
     337              :            "text after from_name string skipped correctly");
     338            2 :     ASSERT(rows[0].complex == 0, "from_name path does not bail");
     339              : 
     340            2 :     transport_close(&t);
     341            2 :     mt_server_reset();
     342              : }
     343              : 
     344            2 : static void test_reply_to_labelled_with_msg_id(void) {
     345            2 :     with_tmp_home("reply-to");
     346            2 :     mt_server_init(); mt_server_reset();
     347            2 :     MtProtoSession s; load_session(&s);
     348            2 :     mt_server_expect(CRC_messages_getHistory, on_history_reply_to, NULL);
     349              : 
     350            2 :     ApiConfig cfg; init_cfg(&cfg);
     351            2 :     Transport t; connect_mock(&t);
     352              : 
     353              :     HistoryEntry rows[4];
     354            2 :     int n = 0;
     355            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     356              :            "reply_to parse succeeds");
     357            2 :     ASSERT(n == 1, "reply-to message preserved");
     358            2 :     ASSERT(rows[0].id == 12346, "id preserved");
     359            2 :     ASSERT(rows[0].date == 1700000800, "outer date preserved");
     360            2 :     ASSERT(strcmp(rows[0].text, "yes") == 0,
     361              :            "text after reply header skipped correctly");
     362            2 :     ASSERT(rows[0].complex == 0, "reply_to branch does not bail");
     363              : 
     364            2 :     transport_close(&t);
     365            2 :     mt_server_reset();
     366              : }
     367              : 
     368            2 : static void test_via_bot_labelled_with_username(void) {
     369            2 :     with_tmp_home("via-bot");
     370            2 :     mt_server_init(); mt_server_reset();
     371            2 :     MtProtoSession s; load_session(&s);
     372            2 :     mt_server_expect(CRC_messages_getHistory, on_history_via_bot, NULL);
     373              : 
     374            2 :     ApiConfig cfg; init_cfg(&cfg);
     375            2 :     Transport t; connect_mock(&t);
     376              : 
     377              :     HistoryEntry rows[4];
     378            2 :     int n = 0;
     379            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     380              :            "via_bot parse succeeds");
     381            2 :     ASSERT(n == 1, "via-bot message preserved");
     382            2 :     ASSERT(rows[0].id == 20001, "id preserved past via_bot int64");
     383            2 :     ASSERT(strcmp(rows[0].text, "<gif>") == 0,
     384              :            "text after via_bot_id i64 skipped correctly");
     385            2 :     ASSERT(rows[0].complex == 0, "via_bot branch does not bail");
     386              : 
     387            2 :     transport_close(&t);
     388            2 :     mt_server_reset();
     389              : }
     390              : 
     391            2 : static void test_via_business_bot_labelled(void) {
     392            2 :     with_tmp_home("via-bizbot");
     393            2 :     mt_server_init(); mt_server_reset();
     394            2 :     MtProtoSession s; load_session(&s);
     395            2 :     mt_server_expect(CRC_messages_getHistory,
     396              :                      on_history_via_business_bot, NULL);
     397              : 
     398            2 :     ApiConfig cfg; init_cfg(&cfg);
     399            2 :     Transport t; connect_mock(&t);
     400              : 
     401              :     HistoryEntry rows[4];
     402            2 :     int n = 0;
     403            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     404              :            "via_business_bot parse succeeds");
     405            2 :     ASSERT(n == 1, "biz-bot message preserved");
     406            2 :     ASSERT(rows[0].id == 20101, "id preserved past flags2.0 i64");
     407            2 :     ASSERT(strcmp(rows[0].text, "auto reply") == 0,
     408              :            "text after via_business_bot_id skipped correctly");
     409            2 :     ASSERT(rows[0].complex == 0, "flags2.0 branch does not bail");
     410              : 
     411            2 :     transport_close(&t);
     412            2 :     mt_server_reset();
     413              : }
     414              : 
     415            2 : static void test_saved_peer_id_suffix(void) {
     416            2 :     with_tmp_home("saved-peer");
     417            2 :     mt_server_init(); mt_server_reset();
     418            2 :     MtProtoSession s; load_session(&s);
     419            2 :     mt_server_expect(CRC_messages_getHistory, on_history_saved_peer, NULL);
     420              : 
     421            2 :     ApiConfig cfg; init_cfg(&cfg);
     422            2 :     Transport t; connect_mock(&t);
     423              : 
     424              :     HistoryEntry rows[4];
     425            2 :     int n = 0;
     426            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     427              :            "saved_peer_id parse succeeds");
     428            2 :     ASSERT(n == 1, "saved-peer message preserved");
     429            2 :     ASSERT(rows[0].id == 30001, "id preserved past saved_peer_id skipper");
     430            2 :     ASSERT(strcmp(rows[0].text, "topic-A msg") == 0,
     431              :            "text after saved_peer_id skipped correctly");
     432            2 :     ASSERT(rows[0].complex == 0, "flags.28 branch does not bail");
     433              : 
     434            2 :     transport_close(&t);
     435            2 :     mt_server_reset();
     436              : }
     437              : 
     438            2 : static void test_multiple_flags_all_rendered(void) {
     439            2 :     with_tmp_home("combo");
     440            2 :     mt_server_init(); mt_server_reset();
     441            2 :     MtProtoSession s; load_session(&s);
     442            2 :     mt_server_expect(CRC_messages_getHistory,
     443              :                      on_history_reply_and_via_bot, NULL);
     444              : 
     445            2 :     ApiConfig cfg; init_cfg(&cfg);
     446            2 :     Transport t; connect_mock(&t);
     447              : 
     448              :     HistoryEntry rows[4];
     449            2 :     int n = 0;
     450            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     451              :            "reply+via_bot combo parse succeeds");
     452            2 :     ASSERT(n == 1, "combo message preserved");
     453            2 :     ASSERT(rows[0].id == 40001, "id preserved with two flag branches");
     454            2 :     ASSERT(strcmp(rows[0].text, "combo reply") == 0,
     455              :            "text survives after via_bot + reply header sequence");
     456            2 :     ASSERT(rows[0].complex == 0, "combo does not bail");
     457              : 
     458            2 :     transport_close(&t);
     459            2 :     mt_server_reset();
     460              : }
     461              : 
     462            2 : static void test_unresolvable_bot_falls_back_to_raw_id(void) {
     463            2 :     with_tmp_home("unresolvable-bot");
     464            2 :     mt_server_init(); mt_server_reset();
     465            2 :     MtProtoSession s; load_session(&s);
     466            2 :     mt_server_expect(CRC_messages_getHistory,
     467              :                      on_history_via_bot_unresolvable, NULL);
     468              : 
     469            2 :     ApiConfig cfg; init_cfg(&cfg);
     470            2 :     Transport t; connect_mock(&t);
     471              : 
     472              :     HistoryEntry rows[4];
     473            2 :     int n = 0;
     474            2 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 4, rows, &n) == 0,
     475              :            "unresolved bot parse succeeds");
     476            2 :     ASSERT(n == 1, "message not dropped when bot id cannot be resolved");
     477            2 :     ASSERT(rows[0].id == 50001, "id preserved for orphan bot line");
     478            2 :     ASSERT(strcmp(rows[0].text, "orphan bot msg") == 0,
     479              :            "text preserved — graceful degradation, not drop");
     480            2 :     ASSERT(rows[0].complex == 0, "orphan-bot path does not flag complex");
     481              : 
     482            2 :     transport_close(&t);
     483            2 :     mt_server_reset();
     484              : }
     485              : 
     486            2 : void run_history_rich_metadata_tests(void) {
     487            2 :     RUN_TEST(test_forwarded_from_channel_labelled);
     488            2 :     RUN_TEST(test_forwarded_from_hidden_user_labelled);
     489            2 :     RUN_TEST(test_reply_to_labelled_with_msg_id);
     490            2 :     RUN_TEST(test_via_bot_labelled_with_username);
     491            2 :     RUN_TEST(test_via_business_bot_labelled);
     492            2 :     RUN_TEST(test_saved_peer_id_suffix);
     493            2 :     RUN_TEST(test_multiple_flags_all_rendered);
     494            2 :     RUN_TEST(test_unresolvable_bot_falls_back_to_raw_id);
     495            2 : }
        

Generated by: LCOV version 2.0-1