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

            Line data    Source code
       1              : /**
       2              :  * @file test_domain_history.c
       3              :  * @brief Unit tests for domain_get_history_self (US-06 v1).
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "domain/read/history.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           14 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
      20              :                                           uint8_t *out, size_t *out_len) {
      21           14 :     TlWriter w; tl_writer_init(&w);
      22           14 :     uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
      23           14 :     uint8_t header[32] = {0};
      24           14 :     uint32_t plen32 = (uint32_t)plen;
      25           14 :     memcpy(header + 28, &plen32, 4);
      26           14 :     tl_write_raw(&w, header, 32);
      27           14 :     tl_write_raw(&w, payload, plen);
      28           14 :     size_t enc = w.len - 24;
      29           14 :     if (enc % 16 != 0) {
      30           12 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
      31              :     }
      32           14 :     out[0] = (uint8_t)(w.len / 4);
      33           14 :     memcpy(out + 1, w.data, w.len);
      34           14 :     *out_len = 1 + w.len;
      35           14 :     tl_writer_free(&w);
      36           14 : }
      37              : 
      38           15 : static void fix_session(MtProtoSession *s) {
      39           15 :     mtproto_session_init(s);
      40           15 :     s->session_id = 0; /* match the zero session_id in fake encrypted frames */
      41           15 :     uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
      42           15 :     mtproto_session_set_salt(s, 0xBADCAFEDEADBEEFULL);
      43           15 : }
      44           15 : static void fix_transport(Transport *t) {
      45           15 :     transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
      46           15 : }
      47           15 : static void fix_cfg(ApiConfig *cfg) {
      48           15 :     api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
      49           15 : }
      50              : 
      51              : /* Build messages.messages containing one messageEmpty entry — enough to
      52              :  * exercise the parse prefix without wrestling with flag-conditional
      53              :  * Message fields. */
      54            1 : static size_t make_one_empty_message(uint8_t *buf, size_t max, int32_t id) {
      55            1 :     TlWriter w; tl_writer_init(&w);
      56            1 :     tl_write_uint32(&w, TL_messages_messages);
      57            1 :     tl_write_uint32(&w, TL_vector);
      58            1 :     tl_write_uint32(&w, 1);                /* vector count */
      59            1 :     tl_write_uint32(&w, TL_messageEmpty);
      60            1 :     tl_write_uint32(&w, 0);                /* flags = 0 */
      61            1 :     tl_write_int32 (&w, id);
      62              : 
      63            1 :     size_t n = w.len < max ? w.len : max;
      64            1 :     memcpy(buf, w.data, n);
      65            1 :     tl_writer_free(&w);
      66            1 :     return n;
      67              : }
      68              : 
      69            1 : static void test_history_one_empty(void) {
      70            1 :     mock_socket_reset(); mock_crypto_reset();
      71              : 
      72              :     uint8_t payload[256];
      73            1 :     size_t plen = make_one_empty_message(payload, sizeof(payload), 1234);
      74              : 
      75            1 :     uint8_t resp[1024]; size_t rlen = 0;
      76            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      77            1 :     mock_socket_set_response(resp, rlen);
      78              : 
      79              :     MtProtoSession s; Transport t; ApiConfig cfg;
      80            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      81              : 
      82            1 :     HistoryEntry entries[5] = {0}; int n = 0;
      83            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, entries, &n);
      84            1 :     ASSERT(rc == 0, "history: must succeed");
      85            1 :     ASSERT(n == 1, "one entry parsed");
      86            1 :     ASSERT(entries[0].id == 1234, "id matches");
      87              : }
      88              : 
      89            1 : static void test_history_rpc_error(void) {
      90            1 :     mock_socket_reset(); mock_crypto_reset();
      91              :     uint8_t payload[128];
      92            1 :     TlWriter w; tl_writer_init(&w);
      93            1 :     tl_write_uint32(&w, TL_rpc_error);
      94            1 :     tl_write_int32(&w, 400);
      95            1 :     tl_write_string(&w, "PEER_ID_INVALID");
      96            1 :     memcpy(payload, w.data, w.len);
      97            1 :     size_t plen = w.len;
      98            1 :     tl_writer_free(&w);
      99              : 
     100            1 :     uint8_t resp[512]; size_t rlen = 0;
     101            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     102            1 :     mock_socket_set_response(resp, rlen);
     103              : 
     104              :     MtProtoSession s; Transport t; ApiConfig cfg;
     105            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     106              : 
     107            1 :     HistoryEntry e[3] = {0}; int n = 0;
     108            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
     109            1 :     ASSERT(rc != 0, "RPC error must propagate");
     110              : }
     111              : 
     112              : /* Build a messages.channelMessages response with one messageEmpty. */
     113            1 : static size_t make_channel_messages(uint8_t *buf, size_t max, int32_t id) {
     114            1 :     TlWriter w; tl_writer_init(&w);
     115            1 :     tl_write_uint32(&w, TL_messages_channelMessages);
     116            1 :     tl_write_uint32(&w, 0);   /* flags */
     117            1 :     tl_write_int32 (&w, 100); /* pts */
     118            1 :     tl_write_int32 (&w, 1);   /* count */
     119            1 :     tl_write_uint32(&w, TL_vector);
     120            1 :     tl_write_uint32(&w, 1);
     121            1 :     tl_write_uint32(&w, TL_messageEmpty);
     122            1 :     tl_write_uint32(&w, 0);   /* flags */
     123            1 :     tl_write_int32 (&w, id);
     124            1 :     size_t n = w.len < max ? w.len : max;
     125            1 :     memcpy(buf, w.data, n);
     126            1 :     tl_writer_free(&w);
     127            1 :     return n;
     128              : }
     129              : 
     130            1 : static void test_history_channel_peer(void) {
     131            1 :     mock_socket_reset(); mock_crypto_reset();
     132              : 
     133              :     uint8_t payload[256];
     134            1 :     size_t plen = make_channel_messages(payload, sizeof(payload), 9999);
     135              : 
     136            1 :     uint8_t resp[1024]; size_t rlen = 0;
     137            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     138            1 :     mock_socket_set_response(resp, rlen);
     139              : 
     140              :     MtProtoSession s; Transport t; ApiConfig cfg;
     141            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     142              : 
     143            1 :     HistoryPeer peer = {
     144              :         .kind = HISTORY_PEER_CHANNEL,
     145              :         .peer_id = 1001234567890LL,
     146              :         .access_hash = 0xABCDEF1234567890LL,
     147              :     };
     148            1 :     HistoryEntry entries[3] = {0}; int n = 0;
     149            1 :     int rc = domain_get_history(&cfg, &s, &t, &peer, 0, 3, entries, &n);
     150            1 :     ASSERT(rc == 0, "channel history parsed");
     151            1 :     ASSERT(n == 1, "one entry");
     152            1 :     ASSERT(entries[0].id == 9999, "id matches");
     153              : }
     154              : 
     155              : /* Build a simple Message (no complex flags) with text = "hello" and
     156              :  * date=1700000000. Flags include out (bit 1) only. */
     157            1 : static size_t make_simple_text_message(uint8_t *buf, size_t max,
     158              :                                          int32_t id, const char *text) {
     159            1 :     TlWriter w; tl_writer_init(&w);
     160            1 :     tl_write_uint32(&w, TL_messages_messages);
     161            1 :     tl_write_uint32(&w, TL_vector);
     162            1 :     tl_write_uint32(&w, 1);
     163            1 :     tl_write_uint32(&w, TL_message);
     164            1 :     tl_write_uint32(&w, (1u << 1));   /* flags: out */
     165            1 :     tl_write_uint32(&w, 0);            /* flags2 */
     166            1 :     tl_write_int32 (&w, id);
     167              :     /* No from_id (flags.8 not set). */
     168            1 :     tl_write_uint32(&w, TL_peerUser); /* peer_id */
     169            1 :     tl_write_int64 (&w, 123LL);
     170            1 :     tl_write_int32 (&w, 1700000000);   /* date */
     171            1 :     tl_write_string(&w, text);
     172            1 :     size_t n = w.len < max ? w.len : max;
     173            1 :     memcpy(buf, w.data, n);
     174            1 :     tl_writer_free(&w);
     175            1 :     return n;
     176              : }
     177              : 
     178            1 : static void test_history_text_extraction(void) {
     179            1 :     mock_socket_reset(); mock_crypto_reset();
     180              : 
     181              :     uint8_t payload[512];
     182            1 :     size_t plen = make_simple_text_message(payload, sizeof(payload),
     183              :                                              55, "hello world");
     184            1 :     uint8_t resp[1024]; size_t rlen = 0;
     185            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     186            1 :     mock_socket_set_response(resp, rlen);
     187              : 
     188              :     MtProtoSession s; Transport t; ApiConfig cfg;
     189            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     190              : 
     191            1 :     HistoryEntry e[3] = {0}; int n = 0;
     192            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
     193            1 :     ASSERT(rc == 0, "simple message parsed");
     194            1 :     ASSERT(n == 1, "one entry");
     195            1 :     ASSERT(e[0].id == 55, "id matches");
     196            1 :     ASSERT(e[0].out == 1, "out flag set");
     197            1 :     ASSERT(e[0].date == 1700000000, "date extracted");
     198            1 :     ASSERT(strcmp(e[0].text, "hello world") == 0, "text extracted");
     199            1 :     ASSERT(e[0].complex == 0, "not complex");
     200              : }
     201              : 
     202            1 : static void test_history_complex_flag(void) {
     203            1 :     mock_socket_reset(); mock_crypto_reset();
     204              : 
     205              :     /* Message with fwd_from flag set — we should mark complex and not
     206              :      * try to parse further. */
     207            1 :     TlWriter w; tl_writer_init(&w);
     208            1 :     tl_write_uint32(&w, TL_messages_messages);
     209            1 :     tl_write_uint32(&w, TL_vector);
     210            1 :     tl_write_uint32(&w, 1);
     211            1 :     tl_write_uint32(&w, TL_message);
     212            1 :     tl_write_uint32(&w, (1u << 2)); /* flags: fwd_from */
     213            1 :     tl_write_uint32(&w, 0);
     214            1 :     tl_write_int32 (&w, 77);
     215              :     /* No further bytes needed — parser bails on complex mask */
     216            1 :     uint8_t payload[128]; memcpy(payload, w.data, w.len);
     217            1 :     size_t plen = w.len; tl_writer_free(&w);
     218              : 
     219            1 :     uint8_t resp[512]; size_t rlen = 0;
     220            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     221            1 :     mock_socket_set_response(resp, rlen);
     222              : 
     223              :     MtProtoSession s; Transport t; ApiConfig cfg;
     224            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     225              : 
     226            1 :     HistoryEntry e[3] = {0}; int n = 0;
     227            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
     228            1 :     ASSERT(rc == 0, "complex message parsed (without text)");
     229            1 :     ASSERT(n == 1, "one entry");
     230            1 :     ASSERT(e[0].id == 77, "id still captured");
     231            1 :     ASSERT(e[0].complex == 1, "complex flag set");
     232            1 :     ASSERT(e[0].text[0] == '\0', "text empty for complex");
     233              : }
     234              : 
     235              : /* Write a simple text Message body only (no top-level wrapper). */
     236            4 : static void write_simple_message(TlWriter *w, int32_t id, const char *text) {
     237            4 :     tl_write_uint32(w, TL_message);
     238            4 :     tl_write_uint32(w, 0);             /* flags: no out, no from_id */
     239            4 :     tl_write_uint32(w, 0);             /* flags2 */
     240            4 :     tl_write_int32 (w, id);
     241            4 :     tl_write_uint32(w, TL_peerUser);   /* peer_id */
     242            4 :     tl_write_int64 (w, 100LL);
     243            4 :     tl_write_int32 (w, 1700000000);    /* date */
     244            4 :     tl_write_string(w, text);
     245            4 : }
     246              : 
     247            1 : static void test_history_iterates_multiple(void) {
     248            1 :     mock_socket_reset(); mock_crypto_reset();
     249              : 
     250              :     /* messages.messages + Vector<Message> with 3 simple messages. */
     251            1 :     TlWriter w; tl_writer_init(&w);
     252            1 :     tl_write_uint32(&w, TL_messages_messages);
     253            1 :     tl_write_uint32(&w, TL_vector);
     254            1 :     tl_write_uint32(&w, 3);
     255            1 :     write_simple_message(&w, 1, "first");
     256            1 :     write_simple_message(&w, 2, "second");
     257            1 :     write_simple_message(&w, 3, "third");
     258            1 :     uint8_t payload[1024]; memcpy(payload, w.data, w.len);
     259            1 :     size_t plen = w.len; tl_writer_free(&w);
     260              : 
     261            1 :     uint8_t resp[2048]; size_t rlen = 0;
     262            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     263            1 :     mock_socket_set_response(resp, rlen);
     264              : 
     265              :     MtProtoSession s; Transport t; ApiConfig cfg;
     266            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     267              : 
     268            1 :     HistoryEntry entries[10] = {0}; int n = 0;
     269            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 10, entries, &n);
     270            1 :     ASSERT(rc == 0, "iteration ok");
     271            1 :     ASSERT(n == 3, "all 3 messages parsed");
     272            1 :     ASSERT(entries[0].id == 1, "id0");
     273            1 :     ASSERT(strcmp(entries[0].text, "first") == 0, "text0");
     274            1 :     ASSERT(entries[1].id == 2, "id1");
     275            1 :     ASSERT(strcmp(entries[1].text, "second") == 0, "text1");
     276            1 :     ASSERT(entries[2].id == 3, "id2");
     277            1 :     ASSERT(strcmp(entries[2].text, "third") == 0, "text2");
     278              : }
     279              : 
     280            1 : static void test_history_iterates_with_entities(void) {
     281            1 :     mock_socket_reset(); mock_crypto_reset();
     282              : 
     283            1 :     TlWriter w; tl_writer_init(&w);
     284            1 :     tl_write_uint32(&w, TL_messages_messages);
     285            1 :     tl_write_uint32(&w, TL_vector);
     286            1 :     tl_write_uint32(&w, 2);
     287              : 
     288              :     /* Msg 1: with entities (flags.7). */
     289            1 :     tl_write_uint32(&w, TL_message);
     290            1 :     tl_write_uint32(&w, (1u << 7));        /* flags: entities present */
     291            1 :     tl_write_uint32(&w, 0);                 /* flags2 */
     292            1 :     tl_write_int32 (&w, 11);
     293            1 :     tl_write_uint32(&w, TL_peerUser);
     294            1 :     tl_write_int64 (&w, 100LL);
     295            1 :     tl_write_int32 (&w, 1700000000);
     296            1 :     tl_write_string(&w, "bold message");
     297              :     /* entities vector: 1 bold */
     298            1 :     tl_write_uint32(&w, TL_vector);
     299            1 :     tl_write_uint32(&w, 1);
     300            1 :     tl_write_uint32(&w, 0xbd610bc9u);       /* messageEntityBold */
     301            1 :     tl_write_int32 (&w, 0);                 /* offset */
     302            1 :     tl_write_int32 (&w, 4);                 /* length */
     303              : 
     304              :     /* Msg 2: plain. */
     305            1 :     write_simple_message(&w, 12, "plain");
     306              : 
     307            1 :     uint8_t payload[1024]; memcpy(payload, w.data, w.len);
     308            1 :     size_t plen = w.len; tl_writer_free(&w);
     309              : 
     310            1 :     uint8_t resp[2048]; size_t rlen = 0;
     311            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     312            1 :     mock_socket_set_response(resp, rlen);
     313              : 
     314              :     MtProtoSession s; Transport t; ApiConfig cfg;
     315            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     316              : 
     317            1 :     HistoryEntry entries[5] = {0}; int n = 0;
     318            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, entries, &n);
     319            1 :     ASSERT(rc == 0, "entities iter ok");
     320            1 :     ASSERT(n == 2, "both messages parsed despite entities");
     321            1 :     ASSERT(strcmp(entries[0].text, "bold message") == 0, "text0");
     322            1 :     ASSERT(strcmp(entries[1].text, "plain") == 0, "text1");
     323              : }
     324              : 
     325              : /* With the MessageMedia skipper, media-bearing messages now iterate. */
     326              : /* Message with messageMediaPhoto carrying a photoEmpty — parser should
     327              :  * populate media=MEDIA_PHOTO, media_id=photo_id. */
     328            1 : static void test_history_media_photo_info(void) {
     329            1 :     mock_socket_reset(); mock_crypto_reset();
     330              : 
     331            1 :     TlWriter w; tl_writer_init(&w);
     332            1 :     tl_write_uint32(&w, TL_messages_messages);
     333            1 :     tl_write_uint32(&w, TL_vector);
     334            1 :     tl_write_uint32(&w, 1);
     335              : 
     336            1 :     tl_write_uint32(&w, TL_message);
     337            1 :     tl_write_uint32(&w, (1u << 9));
     338            1 :     tl_write_uint32(&w, 0);
     339            1 :     tl_write_int32 (&w, 77);
     340            1 :     tl_write_uint32(&w, TL_peerUser);
     341            1 :     tl_write_int64 (&w, 100LL);
     342            1 :     tl_write_int32 (&w, 1700000000);
     343            1 :     tl_write_string(&w, "check this");
     344              :     /* messageMediaPhoto with flags=0x01 (photo present) → photoEmpty#2331b22d id:long */
     345            1 :     tl_write_uint32(&w, 0x695150d7u);    /* messageMediaPhoto */
     346            1 :     tl_write_uint32(&w, (1u << 0));
     347            1 :     tl_write_uint32(&w, 0x2331b22du);    /* photoEmpty */
     348            1 :     tl_write_int64 (&w, 99999999LL);
     349              : 
     350            1 :     uint8_t payload[512]; memcpy(payload, w.data, w.len);
     351            1 :     size_t plen = w.len; tl_writer_free(&w);
     352              : 
     353            1 :     uint8_t resp[1024]; size_t rlen = 0;
     354            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     355            1 :     mock_socket_set_response(resp, rlen);
     356              : 
     357              :     MtProtoSession s; Transport t; ApiConfig cfg;
     358            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     359              : 
     360            1 :     HistoryEntry e[3] = {0}; int n = 0;
     361            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
     362            1 :     ASSERT(rc == 0, "media photo info parsed");
     363            1 :     ASSERT(n == 1, "one entry");
     364            1 :     ASSERT(e[0].media == MEDIA_PHOTO, "kind=photo");
     365            1 :     ASSERT(e[0].media_id == 99999999LL, "photo_id captured");
     366            1 :     ASSERT(strcmp(e[0].text, "check this") == 0, "text preserved");
     367              : }
     368              : 
     369            1 : static void test_history_iterates_with_media_geo(void) {
     370            1 :     mock_socket_reset(); mock_crypto_reset();
     371              : 
     372            1 :     TlWriter w; tl_writer_init(&w);
     373            1 :     tl_write_uint32(&w, TL_messages_messages);
     374            1 :     tl_write_uint32(&w, TL_vector);
     375            1 :     tl_write_uint32(&w, 2);
     376              : 
     377              :     /* Msg 1: flags.9 set + messageMediaGeo (empty geoPoint). */
     378            1 :     tl_write_uint32(&w, TL_message);
     379            1 :     tl_write_uint32(&w, (1u << 9));
     380            1 :     tl_write_uint32(&w, 0);
     381            1 :     tl_write_int32 (&w, 42);
     382            1 :     tl_write_uint32(&w, TL_peerUser);
     383            1 :     tl_write_int64 (&w, 100LL);
     384            1 :     tl_write_int32 (&w, 1700000000);
     385            1 :     tl_write_string(&w, "here");
     386            1 :     tl_write_uint32(&w, 0x56e0d474u); /* messageMediaGeo */
     387            1 :     tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
     388              : 
     389              :     /* Msg 2: plain */
     390            1 :     tl_write_uint32(&w, TL_message);
     391            1 :     tl_write_uint32(&w, 0);
     392            1 :     tl_write_uint32(&w, 0);
     393            1 :     tl_write_int32 (&w, 43);
     394            1 :     tl_write_uint32(&w, TL_peerUser);
     395            1 :     tl_write_int64 (&w, 100LL);
     396            1 :     tl_write_int32 (&w, 1700000001);
     397            1 :     tl_write_string(&w, "there");
     398              : 
     399            1 :     uint8_t payload[1024]; memcpy(payload, w.data, w.len);
     400            1 :     size_t plen = w.len; tl_writer_free(&w);
     401              : 
     402            1 :     uint8_t resp[2048]; size_t rlen = 0;
     403            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     404            1 :     mock_socket_set_response(resp, rlen);
     405              : 
     406              :     MtProtoSession s; Transport t; ApiConfig cfg;
     407            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     408              : 
     409            1 :     HistoryEntry e[5] = {0}; int n = 0;
     410            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
     411            1 :     ASSERT(rc == 0, "iter with geo media");
     412            1 :     ASSERT(n == 2, "both messages iterate past media");
     413            1 :     ASSERT(e[0].id == 42 && strcmp(e[0].text, "here") == 0, "msg0");
     414            1 :     ASSERT(e[1].id == 43 && strcmp(e[1].text, "there") == 0, "msg1");
     415            1 :     ASSERT(e[0].complex == 0, "msg0 NOT complex after media skip");
     416              : }
     417              : 
     418            1 : static void test_history_stops_on_reply_markup(void) {
     419            1 :     mock_socket_reset(); mock_crypto_reset();
     420              : 
     421            1 :     TlWriter w; tl_writer_init(&w);
     422            1 :     tl_write_uint32(&w, TL_messages_messages);
     423            1 :     tl_write_uint32(&w, TL_vector);
     424            1 :     tl_write_uint32(&w, 3);
     425              : 
     426              :     /* Msg 1: flags.6 (reply_markup) set — still no skipper, must bail. */
     427            1 :     tl_write_uint32(&w, TL_message);
     428            1 :     tl_write_uint32(&w, (1u << 6));
     429            1 :     tl_write_uint32(&w, 0);
     430            1 :     tl_write_int32 (&w, 21);
     431            1 :     tl_write_uint32(&w, TL_peerUser);
     432            1 :     tl_write_int64 (&w, 100LL);
     433            1 :     tl_write_int32 (&w, 1700000000);
     434            1 :     tl_write_string(&w, "has reply_markup");
     435              : 
     436              :     /* Msg 2 & 3 would be here but unreachable. */
     437              : 
     438            1 :     uint8_t payload[512]; memcpy(payload, w.data, w.len);
     439            1 :     size_t plen = w.len; tl_writer_free(&w);
     440              : 
     441            1 :     uint8_t resp[1024]; size_t rlen = 0;
     442            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     443            1 :     mock_socket_set_response(resp, rlen);
     444              : 
     445              :     MtProtoSession s; Transport t; ApiConfig cfg;
     446            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     447              : 
     448            1 :     HistoryEntry entries[5] = {0}; int n = 0;
     449            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, entries, &n);
     450            1 :     ASSERT(rc == 0, "reply_markup entry still returned");
     451            1 :     ASSERT(n == 1, "only first captured before iteration stop");
     452            1 :     ASSERT(entries[0].complex == 1, "flagged complex");
     453            1 :     ASSERT(strcmp(entries[0].text, "has reply_markup") == 0, "text before bail");
     454              : }
     455              : 
     456              : /* After phase 3c, a well-formed reply_markup (inline keyboard) no longer
     457              :  * halts iteration — we can parse the message *and* continue to the
     458              :  * next one in the same response. */
     459            1 : static void test_history_iterates_with_reply_markup(void) {
     460            1 :     mock_socket_reset(); mock_crypto_reset();
     461              : 
     462            1 :     TlWriter w; tl_writer_init(&w);
     463            1 :     tl_write_uint32(&w, TL_messages_messages);
     464            1 :     tl_write_uint32(&w, TL_vector);
     465            1 :     tl_write_uint32(&w, 2);
     466              : 
     467              :     /* Msg 1: flags.6 set + replyInlineMarkup with one URL button. */
     468            1 :     tl_write_uint32(&w, TL_message);
     469            1 :     tl_write_uint32(&w, (1u << 6));
     470            1 :     tl_write_uint32(&w, 0);
     471            1 :     tl_write_int32 (&w, 210);
     472            1 :     tl_write_uint32(&w, TL_peerUser);
     473            1 :     tl_write_int64 (&w, 100LL);
     474            1 :     tl_write_int32 (&w, 1700000000);
     475            1 :     tl_write_string(&w, "check this bot");
     476              :     /* reply_markup */
     477            1 :     tl_write_uint32(&w, 0x48a30254u);            /* replyInlineMarkup */
     478            1 :     tl_write_uint32(&w, TL_vector);
     479            1 :     tl_write_uint32(&w, 1);
     480            1 :     tl_write_uint32(&w, 0x77608b83u);            /* keyboardButtonRow */
     481            1 :     tl_write_uint32(&w, TL_vector);
     482            1 :     tl_write_uint32(&w, 1);
     483            1 :     tl_write_uint32(&w, 0x258aff05u);            /* keyboardButtonUrl */
     484            1 :     tl_write_string(&w, "Docs");
     485            1 :     tl_write_string(&w, "https://example.com/docs");
     486              : 
     487              :     /* Msg 2: plain — must be reachable. */
     488            1 :     tl_write_uint32(&w, TL_message);
     489            1 :     tl_write_uint32(&w, 0);
     490            1 :     tl_write_uint32(&w, 0);
     491            1 :     tl_write_int32 (&w, 211);
     492            1 :     tl_write_uint32(&w, TL_peerUser);
     493            1 :     tl_write_int64 (&w, 100LL);
     494            1 :     tl_write_int32 (&w, 1700000001);
     495            1 :     tl_write_string(&w, "after the keyboard");
     496              : 
     497            1 :     uint8_t payload[512]; memcpy(payload, w.data, w.len);
     498            1 :     size_t plen = w.len; tl_writer_free(&w);
     499              : 
     500            1 :     uint8_t resp[1024]; size_t rlen = 0;
     501            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     502            1 :     mock_socket_set_response(resp, rlen);
     503              : 
     504              :     MtProtoSession s; Transport t; ApiConfig cfg;
     505            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     506              : 
     507            1 :     HistoryEntry e[5] = {0}; int n = 0;
     508            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
     509            1 :     ASSERT(rc == 0, "history parses past reply_markup");
     510            1 :     ASSERT(n == 2, "both messages iterate past keyboard");
     511            1 :     ASSERT(e[0].id == 210 && strcmp(e[0].text, "check this bot") == 0,
     512              :            "msg0 text");
     513            1 :     ASSERT(e[0].complex == 0, "msg0 NOT complex after keyboard skip");
     514            1 :     ASSERT(e[1].id == 211 && strcmp(e[1].text, "after the keyboard") == 0,
     515              :            "msg1 text");
     516              : }
     517              : 
     518              : /* Reactions (flags.20) with a simple results vector should also no
     519              :  * longer halt. */
     520            1 : static void test_history_iterates_with_reactions(void) {
     521            1 :     mock_socket_reset(); mock_crypto_reset();
     522              : 
     523            1 :     TlWriter w; tl_writer_init(&w);
     524            1 :     tl_write_uint32(&w, TL_messages_messages);
     525            1 :     tl_write_uint32(&w, TL_vector);
     526            1 :     tl_write_uint32(&w, 2);
     527              : 
     528              :     /* Msg 1: flags.20 = reactions. */
     529            1 :     tl_write_uint32(&w, TL_message);
     530            1 :     tl_write_uint32(&w, (1u << 20));
     531            1 :     tl_write_uint32(&w, 0);
     532            1 :     tl_write_int32 (&w, 310);
     533            1 :     tl_write_uint32(&w, TL_peerUser);
     534            1 :     tl_write_int64 (&w, 100LL);
     535            1 :     tl_write_int32 (&w, 1700000000);
     536            1 :     tl_write_string(&w, "popular message");
     537              :     /* messageReactions: flags=0, one emoji reaction. */
     538            1 :     tl_write_uint32(&w, 0x4f2b9479u);
     539            1 :     tl_write_uint32(&w, 0);
     540            1 :     tl_write_uint32(&w, TL_vector);
     541            1 :     tl_write_uint32(&w, 1);
     542            1 :     tl_write_uint32(&w, 0xa3d1cb80u);   /* reactionCount */
     543            1 :     tl_write_uint32(&w, 0);              /* flags */
     544            1 :     tl_write_uint32(&w, 0x1b2286b8u);   /* reactionEmoji */
     545            1 :     tl_write_string(&w, "\xf0\x9f\x94\xa5"); /* 🔥 */
     546            1 :     tl_write_int32 (&w, 42);
     547              : 
     548              :     /* Msg 2: plain. */
     549            1 :     tl_write_uint32(&w, TL_message);
     550            1 :     tl_write_uint32(&w, 0);
     551            1 :     tl_write_uint32(&w, 0);
     552            1 :     tl_write_int32 (&w, 311);
     553            1 :     tl_write_uint32(&w, TL_peerUser);
     554            1 :     tl_write_int64 (&w, 100LL);
     555            1 :     tl_write_int32 (&w, 1700000001);
     556            1 :     tl_write_string(&w, "next");
     557              : 
     558            1 :     uint8_t payload[512]; memcpy(payload, w.data, w.len);
     559            1 :     size_t plen = w.len; tl_writer_free(&w);
     560              : 
     561            1 :     uint8_t resp[1024]; size_t rlen = 0;
     562            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     563            1 :     mock_socket_set_response(resp, rlen);
     564              : 
     565              :     MtProtoSession s; Transport t; ApiConfig cfg;
     566            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     567              : 
     568            1 :     HistoryEntry e[5] = {0}; int n = 0;
     569            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
     570            1 :     ASSERT(rc == 0, "history parses past reactions");
     571            1 :     ASSERT(n == 2, "both messages iterate past reactions");
     572            1 :     ASSERT(e[0].id == 310 && strcmp(e[0].text, "popular message") == 0,
     573              :            "msg0 text");
     574            1 :     ASSERT(e[0].complex == 0, "msg0 NOT complex after reactions skip");
     575            1 :     ASSERT(e[1].id == 311 && strcmp(e[1].text, "next") == 0, "msg1 text");
     576              : }
     577              : 
     578              : /* Message carrying flags.23 (replies) + flags.22 (restriction_reason)
     579              :  * now iterates — both have skippers. */
     580            1 : static void test_history_iterates_with_replies_and_restriction(void) {
     581            1 :     mock_socket_reset(); mock_crypto_reset();
     582              : 
     583            1 :     TlWriter w; tl_writer_init(&w);
     584            1 :     tl_write_uint32(&w, TL_messages_messages);
     585            1 :     tl_write_uint32(&w, TL_vector);
     586            1 :     tl_write_uint32(&w, 2);
     587              : 
     588              :     /* Msg 1: flags.23 + flags.22 set. */
     589            1 :     tl_write_uint32(&w, TL_message);
     590            1 :     tl_write_uint32(&w, (1u << 23) | (1u << 22));
     591            1 :     tl_write_uint32(&w, 0);
     592            1 :     tl_write_int32 (&w, 410);
     593            1 :     tl_write_uint32(&w, TL_peerUser);
     594            1 :     tl_write_int64 (&w, 100LL);
     595            1 :     tl_write_int32 (&w, 1700000000);
     596            1 :     tl_write_string(&w, "discussion post");
     597              :     /* messageReplies#83d60fc2: flags=0, replies=3, replies_pts=1 */
     598            1 :     tl_write_uint32(&w, 0x83d60fc2u);
     599            1 :     tl_write_uint32(&w, 0);
     600            1 :     tl_write_int32 (&w, 3);
     601            1 :     tl_write_int32 (&w, 1);
     602              :     /* restriction_reason: Vector<RestrictionReason>, 1 entry */
     603            1 :     tl_write_uint32(&w, TL_vector);
     604            1 :     tl_write_uint32(&w, 1);
     605            1 :     tl_write_uint32(&w, 0xd072acb4u);        /* restrictionReason */
     606            1 :     tl_write_string(&w, "android");
     607            1 :     tl_write_string(&w, "sensitive");
     608            1 :     tl_write_string(&w, "Age-restricted");
     609              : 
     610              :     /* Msg 2: plain. */
     611            1 :     tl_write_uint32(&w, TL_message);
     612            1 :     tl_write_uint32(&w, 0);
     613            1 :     tl_write_uint32(&w, 0);
     614            1 :     tl_write_int32 (&w, 411);
     615            1 :     tl_write_uint32(&w, TL_peerUser);
     616            1 :     tl_write_int64 (&w, 100LL);
     617            1 :     tl_write_int32 (&w, 1700000001);
     618            1 :     tl_write_string(&w, "next");
     619              : 
     620            1 :     uint8_t payload[1024]; memcpy(payload, w.data, w.len);
     621            1 :     size_t plen = w.len; tl_writer_free(&w);
     622              : 
     623            1 :     uint8_t resp[2048]; size_t rlen = 0;
     624            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     625            1 :     mock_socket_set_response(resp, rlen);
     626              : 
     627              :     MtProtoSession s; Transport t; ApiConfig cfg;
     628            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     629              : 
     630            1 :     HistoryEntry e[5] = {0}; int n = 0;
     631            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
     632            1 :     ASSERT(rc == 0, "history parses past replies + restriction_reason");
     633            1 :     ASSERT(n == 2, "both messages iterate");
     634            1 :     ASSERT(e[0].id == 410 && strcmp(e[0].text, "discussion post") == 0,
     635              :            "msg0 text");
     636            1 :     ASSERT(e[0].complex == 0, "msg0 NOT complex");
     637            1 :     ASSERT(e[1].id == 411 && strcmp(e[1].text, "next") == 0, "msg1 text");
     638              : }
     639              : 
     640              : /* Test that a media-only message (no caption text) has empty text and non-NONE
     641              :  * media — the fields relied on by the --no-media filter in cmd_history. */
     642            1 : static void test_history_media_only_has_empty_text(void) {
     643            1 :     mock_socket_reset(); mock_crypto_reset();
     644              : 
     645            1 :     TlWriter w; tl_writer_init(&w);
     646            1 :     tl_write_uint32(&w, TL_messages_messages);
     647            1 :     tl_write_uint32(&w, TL_vector);
     648            1 :     tl_write_uint32(&w, 1);
     649              : 
     650              :     /* Message with flags.9 (media) set, empty caption string. */
     651            1 :     tl_write_uint32(&w, TL_message);
     652            1 :     tl_write_uint32(&w, (1u << 9));   /* flags: media present, no out */
     653            1 :     tl_write_uint32(&w, 0);            /* flags2 */
     654            1 :     tl_write_int32 (&w, 500);
     655            1 :     tl_write_uint32(&w, TL_peerUser);
     656            1 :     tl_write_int64 (&w, 100LL);
     657            1 :     tl_write_int32 (&w, 1700000000);
     658            1 :     tl_write_string(&w, "");           /* empty caption */
     659              :     /* messageMediaPhoto with flags=0x01 (photo present) → photoEmpty */
     660            1 :     tl_write_uint32(&w, 0x695150d7u);  /* messageMediaPhoto */
     661            1 :     tl_write_uint32(&w, (1u << 0));
     662            1 :     tl_write_uint32(&w, 0x2331b22du);  /* photoEmpty */
     663            1 :     tl_write_int64 (&w, 88888888LL);
     664              : 
     665            1 :     uint8_t payload[512]; memcpy(payload, w.data, w.len);
     666            1 :     size_t plen = w.len; tl_writer_free(&w);
     667              : 
     668            1 :     uint8_t resp[1024]; size_t rlen = 0;
     669            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     670            1 :     mock_socket_set_response(resp, rlen);
     671              : 
     672              :     MtProtoSession s; Transport t; ApiConfig cfg;
     673            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     674              : 
     675            1 :     HistoryEntry e[3] = {0}; int n = 0;
     676            1 :     int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
     677            1 :     ASSERT(rc == 0,                  "media-only message parsed");
     678            1 :     ASSERT(n == 1,                   "one entry");
     679            1 :     ASSERT(e[0].media == MEDIA_PHOTO, "--no-media filter: media kind is PHOTO");
     680            1 :     ASSERT(e[0].text[0] == '\0',     "--no-media filter: text is empty for media-only");
     681              : }
     682              : 
     683            1 : static void test_history_null_args(void) {
     684            1 :     HistoryEntry e[1]; int n = 0;
     685            1 :     ASSERT(domain_get_history_self(NULL, NULL, NULL, 0, 5, e, &n) == -1,
     686              :            "null args rejected");
     687            1 :     ApiConfig cfg; fix_cfg(&cfg);
     688            1 :     MtProtoSession s; fix_session(&s);
     689            1 :     Transport t; fix_transport(&t);
     690            1 :     ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 0, e, &n) == -1,
     691              :            "limit=0 rejected");
     692              : }
     693              : 
     694            1 : void run_domain_history_tests(void) {
     695            1 :     RUN_TEST(test_history_one_empty);
     696            1 :     RUN_TEST(test_history_rpc_error);
     697            1 :     RUN_TEST(test_history_channel_peer);
     698            1 :     RUN_TEST(test_history_text_extraction);
     699            1 :     RUN_TEST(test_history_complex_flag);
     700            1 :     RUN_TEST(test_history_iterates_multiple);
     701            1 :     RUN_TEST(test_history_iterates_with_entities);
     702            1 :     RUN_TEST(test_history_iterates_with_media_geo);
     703            1 :     RUN_TEST(test_history_media_photo_info);
     704            1 :     RUN_TEST(test_history_stops_on_reply_markup);
     705            1 :     RUN_TEST(test_history_iterates_with_reply_markup);
     706            1 :     RUN_TEST(test_history_iterates_with_reactions);
     707            1 :     RUN_TEST(test_history_iterates_with_replies_and_restriction);
     708            1 :     RUN_TEST(test_history_media_only_has_empty_text);
     709            1 :     RUN_TEST(test_history_null_args);
     710            1 : }
        

Generated by: LCOV version 2.0-1