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

            Line data    Source code
       1              : /**
       2              :  * @file test_send_stdin.c
       3              :  * @brief TEST-13 — stdin pipe → domain_send_message functional test.
       4              :  *
       5              :  * Validates the stdin-reading branch used by `cmd_send` when no inline
       6              :  * message is provided:
       7              :  *   1. Redirect stdin to a pipe containing "hello from pipe\n".
       8              :  *   2. Read from stdin exactly as cmd_send does (fread + strip newline).
       9              :  *   3. Call domain_send_message with the resulting text.
      10              :  *   4. Assert the mock server received "hello from pipe" in the TL wire.
      11              :  *
      12              :  * The test does NOT call the static cmd_send() in tg_cli.c directly; it
      13              :  * replicates the exact stdin-read idiom so the coverage is equivalent.
      14              :  */
      15              : 
      16              : #include "test_helpers.h"
      17              : 
      18              : #include "mock_socket.h"
      19              : #include "mock_tel_server.h"
      20              : 
      21              : #include "api_call.h"
      22              : #include "mtproto_session.h"
      23              : #include "transport.h"
      24              : #include "app/session_store.h"
      25              : #include "tl_serial.h"
      26              : 
      27              : #include "domain/write/send.h"
      28              : 
      29              : #include <stdio.h>
      30              : #include <stdlib.h>
      31              : #include <string.h>
      32              : #include <unistd.h>
      33              : 
      34              : /* ------------------------------------------------------------------ */
      35              : /* Helpers shared with test_write_path.c (duplicated to stay simple). */
      36              : /* ------------------------------------------------------------------ */
      37              : 
      38              : #define CRC_messages_sendMessage   0x0d9d75a4U
      39              : #define CRC_updateShortSentMessage 0x9015e101U
      40              : 
      41            4 : static void with_tmp_home_stdin(const char *tag) {
      42              :     char tmp[256];
      43            4 :     snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-stdin-%s", tag);
      44              :     char bin[512];
      45            4 :     snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
      46            4 :     (void)unlink(bin);
      47            4 :     setenv("HOME", tmp, 1);
      48            4 : }
      49              : 
      50            4 : static void connect_mock_stdin(Transport *t) {
      51            4 :     transport_init(t);
      52            4 :     ASSERT(transport_connect(t, "127.0.0.1", 443) == 0, "connect");
      53              : }
      54              : 
      55            4 : static void init_cfg_stdin(ApiConfig *cfg) {
      56            4 :     api_config_init(cfg);
      57            4 :     cfg->api_id   = 12345;
      58            4 :     cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
      59            4 : }
      60              : 
      61            4 : static void load_session_stdin(MtProtoSession *s) {
      62            4 :     ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed");
      63            4 :     mtproto_session_init(s);
      64            4 :     int dc = 0;
      65            4 :     ASSERT(session_store_load(s, &dc) == 0, "load");
      66              : }
      67              : 
      68              : /* ------------------------------------------------------------------ */
      69              : /* State captured by the responder.                                    */
      70              : /* ------------------------------------------------------------------ */
      71              : 
      72              : static char g_captured_message[4096];
      73              : 
      74              : /* ------------------------------------------------------------------ */
      75              : /* Responder: parse messages.sendMessage, capture the 'message' field. */
      76              : /*                                                                      */
      77              : /* Wire layout (after invokeWithLayer / initConnection stripped):       */
      78              : /*   CRC        uint32  0x0d9d75a4                                      */
      79              : /*   flags      uint32                                                  */
      80              : /*   peer       inputPeerSelf = uint32 TL_inputPeerSelf                 */
      81              : /*   message    TL string (length-prefixed)                             */
      82              : /*   random_id  int64                                                   */
      83              : /* ------------------------------------------------------------------ */
      84            2 : static void on_send_stdin(MtRpcContext *ctx) {
      85            2 :     g_captured_message[0] = '\0';
      86              : 
      87            2 :     TlReader r = tl_reader_init(ctx->req_body, ctx->req_body_len);
      88            2 :     tl_read_uint32(&r);   /* CRC */
      89            2 :     tl_read_uint32(&r);   /* flags */
      90            2 :     tl_read_uint32(&r);   /* inputPeerSelf constructor */
      91            2 :     char *msg = tl_read_string(&r);
      92            2 :     if (msg) {
      93            2 :         snprintf(g_captured_message, sizeof(g_captured_message), "%s", msg);
      94            2 :         free(msg);
      95              :     }
      96              : 
      97              :     /* Reply with updateShortSentMessage so domain_send_message returns 0. */
      98            2 :     TlWriter w; tl_writer_init(&w);
      99            2 :     tl_write_uint32(&w, CRC_updateShortSentMessage);
     100            2 :     tl_write_uint32(&w, 0);    /* flags */
     101            2 :     tl_write_int32 (&w, 777);  /* id */
     102            2 :     tl_write_int32 (&w, 0);    /* pts */
     103            2 :     tl_write_int32 (&w, 0);    /* pts_count */
     104            2 :     tl_write_int32 (&w, 0);    /* date */
     105            2 :     mt_server_reply_result(ctx, w.data, w.len);
     106            2 :     tl_writer_free(&w);
     107            2 : }
     108              : 
     109              : /* ------------------------------------------------------------------ */
     110              : /* Tests                                                                */
     111              : /* ------------------------------------------------------------------ */
     112              : 
     113              : /**
     114              :  * Happy path: pipe "hello from pipe\n" into stdin, read it as cmd_send
     115              :  * does, pass to domain_send_message, assert the server sees the text.
     116              :  */
     117            2 : static void test_send_from_stdin_pipe(void) {
     118            2 :     with_tmp_home_stdin("pipe");
     119            2 :     mt_server_init(); mt_server_reset();
     120            2 :     MtProtoSession s; load_session_stdin(&s);
     121            2 :     mt_server_expect(CRC_messages_sendMessage, on_send_stdin, NULL);
     122              : 
     123            2 :     ApiConfig cfg; init_cfg_stdin(&cfg);
     124            2 :     Transport t; connect_mock_stdin(&t);
     125              : 
     126              :     /* Set up a pipe and redirect stdin to its read end. */
     127              :     int pipefd[2];
     128            2 :     ASSERT(pipe(pipefd) == 0, "pipe() created");
     129            2 :     const char *pipe_content = "hello from pipe\n";
     130            2 :     ssize_t written = write(pipefd[1], pipe_content, strlen(pipe_content));
     131            2 :     ASSERT(written == (ssize_t)strlen(pipe_content), "wrote to pipe");
     132            2 :     close(pipefd[1]);
     133              : 
     134            2 :     int saved_stdin = dup(STDIN_FILENO);
     135            2 :     ASSERT(saved_stdin >= 0, "dup stdin");
     136            2 :     ASSERT(dup2(pipefd[0], STDIN_FILENO) == STDIN_FILENO, "dup2 stdin");
     137            2 :     close(pipefd[0]);
     138              : 
     139              :     /* --- Replicate cmd_send's stdin-read idiom --- */
     140              :     char stdin_buf[4096];
     141            2 :     size_t n = fread(stdin_buf, 1, sizeof(stdin_buf) - 1, stdin);
     142            2 :     ASSERT(n > 0, "fread from pipe got bytes");
     143            2 :     stdin_buf[n] = '\0';
     144              :     /* Strip one trailing newline for convenience (same as cmd_send). */
     145            2 :     if (n > 0 && stdin_buf[n - 1] == '\n') stdin_buf[n - 1] = '\0';
     146            2 :     const char *msg = stdin_buf;
     147              : 
     148              :     /* Restore stdin so subsequent test output is not affected. */
     149            2 :     ASSERT(dup2(saved_stdin, STDIN_FILENO) == STDIN_FILENO, "restore stdin");
     150            2 :     close(saved_stdin);
     151              : 
     152              :     /* --- Send via domain layer --- */
     153            2 :     int32_t mid = 0;
     154            2 :     RpcError err = {0};
     155            2 :     ASSERT(domain_send_message(&cfg, &s, &t, &(HistoryPeer){.kind = HISTORY_PEER_SELF},
     156              :                                msg, &mid, &err) == 0,
     157              :            "domain_send_message succeeds");
     158            2 :     ASSERT(mid == 777, "message id echoed from mock server");
     159            2 :     ASSERT(strcmp(g_captured_message, "hello from pipe") == 0,
     160              :            "server received 'hello from pipe' (newline stripped)");
     161              : 
     162            2 :     transport_close(&t);
     163            2 :     mt_server_reset();
     164              : }
     165              : 
     166              : /**
     167              :  * Empty stdin should not reach the server (domain_send_message rejects
     168              :  * empty strings before the wire).
     169              :  */
     170            2 : static void test_send_empty_stdin_rejected(void) {
     171            2 :     with_tmp_home_stdin("empty");
     172            2 :     mt_server_init(); mt_server_reset();
     173            2 :     MtProtoSession s; load_session_stdin(&s);
     174              :     /* No handler — wire must not be touched. */
     175              : 
     176            2 :     ApiConfig cfg; init_cfg_stdin(&cfg);
     177            2 :     Transport t; connect_mock_stdin(&t);
     178              : 
     179              :     /* domain_send_message rejects "" before sending. */
     180            2 :     int32_t mid = 0;
     181            2 :     RpcError err = {0};
     182            2 :     ASSERT(domain_send_message(&cfg, &s, &t,
     183              :                                &(HistoryPeer){.kind = HISTORY_PEER_SELF},
     184              :                                "", &mid, &err) == -1,
     185              :            "empty message rejected");
     186            2 :     ASSERT(mt_server_rpc_call_count() == 0, "no RPC dispatched for empty");
     187              : 
     188            2 :     transport_close(&t);
     189            2 :     mt_server_reset();
     190              : }
     191              : 
     192              : /* ------------------------------------------------------------------ */
     193              : /* Suite entry point                                                    */
     194              : /* ------------------------------------------------------------------ */
     195              : 
     196            2 : void run_send_stdin_tests(void) {
     197            2 :     RUN_TEST(test_send_from_stdin_pipe);
     198            2 :     RUN_TEST(test_send_empty_stdin_rejected);
     199            2 : }
        

Generated by: LCOV version 2.0-1