Line data Source code
1 : /**
2 : * @file test_dialogs_cache_ttl.c
3 : * @brief TEST-04 — dialogs cache TTL functional tests.
4 : *
5 : * Verifies that domain_get_dialogs:
6 : * 1. Serves a second consecutive call from the in-memory cache (no RPC).
7 : * 2. Issues a fresh RPC once the TTL has expired.
8 : *
9 : * The production `DIALOGS_CACHE_TTL_S` is 60 seconds. Rather than
10 : * sleeping, we replace the TTL clock with a fake via
11 : * `dialogs_cache_set_now_fn()` and advance it explicitly.
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 : #include "domain/read/dialogs.h"
26 :
27 : #include <stdio.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 : #include <unistd.h>
31 : #include <time.h>
32 :
33 : /* ---- CRCs ---- */
34 : #define CRC_messages_getDialogs 0xa0f4cb4fU
35 : #define CRC_dialog 0xd58a08c6U
36 : #define CRC_peerNotifySettings 0xa83b0426U
37 :
38 : /* ---- Fake clock ---- */
39 :
40 : static time_t s_fake_time = 0;
41 :
42 10 : static time_t fake_now(void) { return s_fake_time; }
43 :
44 : /* ---- Helpers ---- */
45 :
46 2 : static void with_tmp_home(const char *tag) {
47 : char tmp[256];
48 2 : snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-dlg-ttl-%s", tag);
49 : char bin[512];
50 2 : snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
51 2 : (void)unlink(bin);
52 2 : setenv("HOME", tmp, 1);
53 2 : }
54 :
55 2 : static void connect_mock(Transport *t) {
56 2 : transport_init(t);
57 2 : ASSERT(transport_connect(t, "127.0.0.1", 443) == 0, "connect");
58 : }
59 :
60 2 : static void init_cfg(ApiConfig *cfg) {
61 2 : api_config_init(cfg);
62 2 : cfg->api_id = 12345;
63 2 : cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
64 2 : }
65 :
66 2 : static void load_session(MtProtoSession *s) {
67 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed");
68 2 : mtproto_session_init(s);
69 2 : int dc = 0;
70 2 : ASSERT(session_store_load(s, &dc) == 0, "load session");
71 : }
72 :
73 : /* ---- Responder: one user dialog ---- */
74 :
75 4 : static void on_dialogs_one(MtRpcContext *ctx) {
76 : TlWriter w;
77 4 : tl_writer_init(&w);
78 4 : tl_write_uint32(&w, TL_messages_dialogs);
79 :
80 4 : tl_write_uint32(&w, TL_vector);
81 4 : tl_write_uint32(&w, 1);
82 4 : tl_write_uint32(&w, CRC_dialog);
83 4 : tl_write_uint32(&w, 0); /* flags=0 */
84 4 : tl_write_uint32(&w, TL_peerUser);
85 4 : tl_write_int64 (&w, 42LL); /* peer_id */
86 4 : tl_write_int32 (&w, 100); /* top_message */
87 4 : tl_write_int32 (&w, 0); /* read_inbox_max_id */
88 4 : tl_write_int32 (&w, 0); /* read_outbox_max_id */
89 4 : tl_write_int32 (&w, 5); /* unread_count */
90 4 : tl_write_int32 (&w, 0); /* unread_mentions_count */
91 4 : tl_write_int32 (&w, 0); /* unread_reactions_count */
92 4 : tl_write_uint32(&w, CRC_peerNotifySettings);
93 4 : tl_write_uint32(&w, 0);
94 :
95 4 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0); /* messages */
96 4 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0); /* chats */
97 4 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0); /* users */
98 :
99 4 : mt_server_reply_result(ctx, w.data, w.len);
100 4 : tl_writer_free(&w);
101 4 : }
102 :
103 : /* ================================================================ */
104 : /* Tests */
105 : /* ================================================================ */
106 :
107 : /**
108 : * @brief First assert: two consecutive calls within TTL fire exactly ONE RPC.
109 : * Second assert: a third call after TTL expiry fires a second RPC.
110 : */
111 2 : static void test_dialogs_cache_ttl(void) {
112 2 : with_tmp_home("ttl");
113 2 : mt_server_init(); mt_server_reset();
114 2 : MtProtoSession s; load_session(&s);
115 :
116 : /* Arm the fake clock at t=0. */
117 2 : s_fake_time = 1000;
118 2 : dialogs_cache_set_now_fn(fake_now);
119 2 : dialogs_cache_flush();
120 :
121 2 : mt_server_expect(CRC_messages_getDialogs, on_dialogs_one, NULL);
122 :
123 2 : ApiConfig cfg; init_cfg(&cfg);
124 2 : Transport t; connect_mock(&t);
125 :
126 : DialogEntry rows[8];
127 2 : int n = 0;
128 :
129 : /* ---- Call 1: cold cache — must hit the mock server. ---- */
130 2 : ASSERT(domain_get_dialogs(&cfg, &s, &t, 8, 0, rows, &n, NULL) == 0,
131 : "call-1 ok");
132 2 : ASSERT(n == 1, "call-1 returns 1 dialog");
133 2 : ASSERT(rows[0].peer_id == 42LL, "call-1 peer_id==42");
134 2 : ASSERT(mt_server_rpc_call_count() == 1, "call-1 RPC count == 1");
135 :
136 : /* ---- Call 2: within TTL — must be served from cache, no new RPC. ---- */
137 2 : n = 0;
138 2 : s_fake_time = 1030; /* +30 s, still within 60-s TTL */
139 2 : ASSERT(domain_get_dialogs(&cfg, &s, &t, 8, 0, rows, &n, NULL) == 0,
140 : "call-2 ok");
141 2 : ASSERT(n == 1, "call-2 returns 1 dialog from cache");
142 2 : ASSERT(rows[0].peer_id == 42LL, "call-2 peer_id==42 (from cache)");
143 2 : ASSERT(mt_server_rpc_call_count() == 1, "call-2 still only 1 RPC total");
144 :
145 : /* ---- Call 3: advance clock past TTL — must hit the mock server again. ---- */
146 2 : n = 0;
147 2 : s_fake_time = 1070; /* +70 s from fetch time → TTL expired */
148 2 : ASSERT(domain_get_dialogs(&cfg, &s, &t, 8, 0, rows, &n, NULL) == 0,
149 : "call-3 ok");
150 2 : ASSERT(n == 1, "call-3 returns 1 dialog (fresh RPC)");
151 2 : ASSERT(rows[0].peer_id == 42LL, "call-3 peer_id==42");
152 2 : ASSERT(mt_server_rpc_call_count() == 2, "call-3 triggers second RPC");
153 :
154 : /* Restore real clock. */
155 2 : dialogs_cache_set_now_fn(NULL);
156 :
157 2 : transport_close(&t);
158 2 : mt_server_reset();
159 : }
160 :
161 2 : void run_dialogs_cache_ttl_tests(void) {
162 2 : RUN_TEST(test_dialogs_cache_ttl);
163 2 : }
|