Coverage Report

Created: 2026-10-24 08:16

/work/auto_tests/scenarios/scenario_group_moderation_test.c
Line
Count
Source (jump to first uncovered line)
1
#include "framework/framework.h"
2
#include <stdio.h>
3
#include <string.h>
4
#include <stdlib.h>
5
6
188
#define NUM_PEERS 5
7
2
#define GROUP_NAME "Moderation Test Group"
8
1
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
9
10
typedef struct {
11
    uint32_t group_number;
12
    uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
13
    bool chat_id_ready;
14
    uint32_t peer_count;
15
    bool connected;
16
    Tox_Group_Role self_role;
17
    Tox_Group_Voice_State voice_state;
18
    bool kicked;
19
    uint32_t peer_ids[NUM_PEERS];    // Map node index to group peer_id
20
    Tox_Group_Mod_Event last_mod_event;
21
    uint32_t last_mod_target;
22
} ModState;
23
24
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
25
13
{
26
13
    ToxNode *self = (ToxNode *)user_data;
27
13
    ModState *state = (ModState *)tox_node_get_script_ctx(self);
28
13
    state->connected = true;
29
13
    uint32_t group_number = tox_event_group_self_join_get_group_number(event);
30
13
    uint32_t self_id = tox_group_self_get_peer_id(tox_node_get_tox(self), group_number, nullptr);
31
13
    state->self_role = tox_group_self_get_role(tox_node_get_tox(self), group_number, nullptr);
32
13
    tox_node_log(self, "Joined group %u (Peer ID: %u) with role %s", group_number, self_id, tox_group_role_to_string(state->self_role));
33
13
}
34
35
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
36
20
{
37
20
    ToxNode *self = (ToxNode *)user_data;
38
20
    ModState *state = (ModState *)tox_node_get_script_ctx(self);
39
20
    uint32_t group_number = tox_event_group_peer_join_get_group_number(event);
40
20
    uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
41
42
20
    tox_node_log(self, "Peer %u joined the group", peer_id);
43
20
    state->peer_count++;
44
45
20
    Tox_Err_Group_Peer_Query q_err;
46
20
    size_t length = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err);
47
20
    if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) {
48
20
        uint8_t name[TOX_MAX_NAME_LENGTH];
49
20
        tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err);
50
20
        if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) {
51
20
            tox_node_log(self, "Peer %u name identified: %.*s", peer_id, (int)length, name);
52
20
            if (length == 7 && memcmp(name, "Founder", 7) == 0) {
53
4
                state->peer_ids[0] = peer_id;
54
16
            } else if (length >= 5 && memcmp(name, "Peer", 4) == 0) {
55
16
                int idx = atoi((const char *)name + 4);
56
16
                if (idx > 0 && idx < NUM_PEERS) {
57
16
                    state->peer_ids[idx] = peer_id;
58
16
                }
59
16
            }
60
20
        }
61
20
    }
62
20
}
63
64
static void on_group_moderation(const Tox_Event_Group_Moderation *event, void *user_data)
65
45
{
66
45
    ToxNode *self = (ToxNode *)user_data;
67
45
    ModState *state = (ModState *)tox_node_get_script_ctx(self);
68
45
    state->last_mod_event = tox_event_group_moderation_get_mod_type(event);
69
45
    state->last_mod_target = tox_event_group_moderation_get_target_peer_id(event);
70
71
45
    Tox_Err_Group_Self_Query err;
72
45
    state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, &err);
73
74
45
    if (state->last_mod_event == TOX_GROUP_MOD_EVENT_KICK && state->last_mod_target == tox_group_self_get_peer_id(tox_node_get_tox(self), state->group_number, nullptr)) {
75
1
        state->kicked = true;
76
1
    }
77
78
45
    tox_node_log(self, "Moderation event: %s on peer %u. My role is now %s",
79
45
                 tox_group_mod_event_to_string(state->last_mod_event),
80
45
                 state->last_mod_target,
81
45
                 tox_group_role_to_string(state->self_role));
82
45
}
83
84
static void on_group_voice_state(const Tox_Event_Group_Voice_State *event, void *user_data)
85
16
{
86
16
    ToxNode *self = (ToxNode *)user_data;
87
16
    ModState *state = (ModState *)tox_node_get_script_ctx(self);
88
16
    state->voice_state = tox_event_group_voice_state_get_voice_state(event);
89
16
    tox_node_log(self, "Voice state updated: %u", state->voice_state);
90
16
}
91
92
static void common_init(ToxNode *self, ModState *state)
93
8
{
94
8
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
95
8
    tox_events_callback_group_self_join(dispatch, on_group_self_join);
96
8
    tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
97
8
    tox_events_callback_group_moderation(dispatch, on_group_moderation);
98
8
    tox_events_callback_group_voice_state(dispatch, on_group_voice_state);
99
100
42
    for (int i = 0; i < NUM_PEERS; ++i) {
101
34
        state->peer_ids[i] = UINT32_MAX;
102
34
    }
103
104
8
    tox_node_log(self, "Waiting for self connection...");
105
8
    tox_node_wait_for_self_connected(self);
106
8
    tox_node_log(self, "Connected!");
107
8
}
108
109
static void wait_for_peer_role(ToxNode *self, uint32_t peer_idx, Tox_Group_Role expected_role)
110
29
{
111
29
    ModState *state = (ModState *)tox_node_get_script_ctx(self);
112
29
    tox_node_log(self, "Waiting to see Peer %u with role %s", peer_idx, tox_group_role_to_string(expected_role));
113
114
59
    while (tox_scenario_is_running(self)) {
115
59
        uint32_t peer_id = state->peer_ids[peer_idx];
116
117
59
        if (peer_id != UINT32_MAX) {
118
59
            Tox_Err_Group_Peer_Query err;
119
59
            Tox_Group_Role role = tox_group_peer_get_role(tox_node_get_tox(self), state->group_number, peer_id, &err);
120
121
59
            if (err == TOX_ERR_GROUP_PEER_QUERY_OK && role == expected_role) {
122
29
                break;
123
29
            }
124
59
        }
125
126
30
        tox_scenario_yield(self);
127
30
    }
128
129
29
    tox_node_log(self, "Peer %u now has role %s", peer_idx, tox_group_role_to_string(expected_role));
130
29
}
131
132
static void founder_script(ToxNode *self, void *ctx)
133
1
{
134
1
    ModState *state = (ModState *)ctx;
135
1
    Tox *tox = tox_node_get_tox(self);
136
1
    common_init(self, state);
137
138
1
    Tox_Err_Group_New err_new;
139
1
    state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new);
140
1
    ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
141
142
1
    state->self_role = TOX_GROUP_ROLE_FOUNDER;
143
1
    state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
144
145
1
    tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
146
1
    state->chat_id_ready = true;
147
148
    // Barrier 1: Wait for all peers to join and be seen by everyone
149
1
    WAIT_UNTIL(state->peer_count == NUM_PEERS - 1);
150
1
    tox_node_log(self, "All peers joined");
151
152
    // Wait until we know all peer IDs
153
5
    for (int i = 1; i < NUM_PEERS; ++i) {
154
4
        WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
155
4
    }
156
1
    tox_node_log(self, "All peer IDs identified");
157
158
1
    tox_scenario_barrier_wait(self); // Barrier 1: Everyone sees everyone
159
160
    // Barrier 2: Peer 1 becomes Moderator
161
1
    tox_group_set_role(tox, state->group_number, state->peer_ids[1], TOX_GROUP_ROLE_MODERATOR, nullptr);
162
1
    wait_for_peer_role(self, 1, TOX_GROUP_ROLE_MODERATOR);
163
1
    tox_scenario_barrier_wait(self); // Barrier 2: Everyone sees Peer 1 as Moderator
164
165
    // Barrier 3: Peer 2 and 3 become Observer
166
1
    tox_group_set_role(tox, state->group_number, state->peer_ids[2], TOX_GROUP_ROLE_OBSERVER, nullptr);
167
1
    tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_OBSERVER, nullptr);
168
1
    wait_for_peer_role(self, 2, TOX_GROUP_ROLE_OBSERVER);
169
1
    wait_for_peer_role(self, 3, TOX_GROUP_ROLE_OBSERVER);
170
1
    tox_scenario_barrier_wait(self); // Barrier 3: Everyone sees Peer 2/3 as Observers
171
172
    // Barrier 4: Voice State tests
173
1
    tox_node_log(self, "Setting voice state to MODERATOR");
174
1
    tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, nullptr);
175
1
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 1 set
176
1
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 1 done
177
178
1
    tox_node_log(self, "Setting voice state to FOUNDER");
179
1
    tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_FOUNDER, nullptr);
180
1
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 2 set
181
1
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 2 done
182
183
1
    tox_node_log(self, "Setting voice state to ALL");
184
1
    tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, nullptr);
185
1
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 3 set
186
1
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 3 done
187
188
    // Barrier 5: Peer 1 (Mod) promotes Peer 2 back to User
189
1
    wait_for_peer_role(self, 2, TOX_GROUP_ROLE_USER);
190
1
    tox_scenario_barrier_wait(self); // Barrier 5: Everyone sees Peer 2 as User
191
192
    // Barrier 6: Founder promotes Peer 3 to Moderator
193
1
    tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_MODERATOR, nullptr);
194
1
    wait_for_peer_role(self, 3, TOX_GROUP_ROLE_MODERATOR);
195
1
    tox_scenario_barrier_wait(self); // Barrier 6: Everyone sees Peer 3 as Moderator
196
197
    // Barrier 7: Moderator (Peer 1) attempts to kick/demote Founder (should fail)
198
1
    tox_scenario_barrier_wait(self); // Barrier 7: Founder wait
199
200
    // Barrier 8: Founder kicks Moderator (Peer 1)
201
1
    tox_group_kick_peer(tox, state->group_number, state->peer_ids[1], nullptr);
202
1
    tox_scenario_barrier_wait(self); // Barrier 8: Founder wait
203
204
    // Barrier 9: Founder demotes Moderator (Peer 3) to User
205
1
    tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_USER, nullptr);
206
1
    wait_for_peer_role(self, 3, TOX_GROUP_ROLE_USER);
207
1
    tox_scenario_barrier_wait(self); // Barrier 9: Founder wait
208
209
1
    tox_scenario_barrier_wait(self); // Done
210
1
}
211
212
static void peer_script(ToxNode *self, void *ctx)
213
4
{
214
4
    ModState *state = (ModState *)ctx;
215
4
    Tox *tox = tox_node_get_tox(self);
216
4
    common_init(self, state);
217
218
4
    const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
219
4
    const ModState *founder_view = (const ModState *)tox_node_get_peer_ctx(founder);
220
221
5
    while (!founder_view->chat_id_ready) {
222
1
        tox_scenario_yield(self);
223
1
    }
224
4
    tox_node_log(self, "Got chat ID from founder");
225
226
4
    char name[16];
227
4
    snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self));
228
4
    Tox_Err_Group_Join err_join;
229
4
    state->group_number = tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, &err_join);
230
4
    if (state->group_number == UINT32_MAX) {
231
0
        tox_node_log(self, "tox_group_join failed with error %u", err_join);
232
0
    }
233
4
    ck_assert(state->group_number != UINT32_MAX);
234
235
4
    WAIT_UNTIL(state->connected);
236
237
4
    uint32_t self_id = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
238
4
    state->peer_ids[tox_node_get_index(self)] = self_id;
239
240
    // Barrier 1: Joined
241
    // Wait until we know all peer IDs
242
24
    for (uint32_t i = 0; i < NUM_PEERS; ++i) {
243
20
        if (tox_node_get_index(self) != i) {
244
16
            WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
245
16
        }
246
20
    }
247
4
    tox_scenario_barrier_wait(self); // Barrier 1: Everyone sees everyone
248
249
    // Barrier 2: Peer 1 Moderator
250
4
    wait_for_peer_role(self, 1, TOX_GROUP_ROLE_MODERATOR);
251
4
    tox_scenario_barrier_wait(self); // Barrier 2: Everyone sees Peer 1 as Moderator
252
253
    // Barrier 3: Peer 2/3 Observer
254
4
    wait_for_peer_role(self, 2, TOX_GROUP_ROLE_OBSERVER);
255
4
    wait_for_peer_role(self, 3, TOX_GROUP_ROLE_OBSERVER);
256
4
    tox_scenario_barrier_wait(self); // Barrier 3: Everyone sees Peer 2/3 as Observers
257
258
    // Barrier 4: Voice State tests
259
    // Sub-phase 1: MODERATOR
260
4
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 1 set
261
4
    WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_MODERATOR);
262
4
    Tox_Err_Group_Send_Message err_msg;
263
4
    tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg);
264
4
    if (state->self_role == TOX_GROUP_ROLE_MODERATOR || state->self_role == TOX_GROUP_ROLE_FOUNDER) {
265
1
        if (err_msg != TOX_ERR_GROUP_SEND_MESSAGE_OK) {
266
0
            tox_node_log(self, "Expected OK, got %u. Role: %s", err_msg, tox_group_role_to_string(state->self_role));
267
0
        }
268
1
        ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK);
269
3
    } else {
270
3
        if (err_msg != TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS) {
271
0
            tox_node_log(self, "Expected PERMISSIONS, got %u. Role: %s", err_msg, tox_group_role_to_string(state->self_role));
272
0
        }
273
3
        ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS);
274
3
    }
275
4
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 1 done
276
277
    // Sub-phase 2: FOUNDER
278
4
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 2 set
279
4
    WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_FOUNDER);
280
4
    tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg);
281
4
    if (state->self_role == TOX_GROUP_ROLE_FOUNDER) {
282
0
        ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK);
283
4
    } else {
284
4
        ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS);
285
4
    }
286
4
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 2 done
287
288
    // Sub-phase 3: ALL
289
4
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 3 set
290
4
    WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_ALL);
291
4
    tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg);
292
4
    if (state->self_role == TOX_GROUP_ROLE_OBSERVER) {
293
2
        ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS);
294
2
    } else {
295
2
        ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK);
296
2
    }
297
4
    tox_scenario_barrier_wait(self); // Barrier 4 Phase 3 done
298
299
    // Barrier 5: Peer 1 (Mod) promotes Peer 2 back to User
300
4
    if (tox_node_get_index(self) == 1) { // Peer 1
301
1
        uint32_t peer2_id = state->peer_ids[2];
302
1
        tox_group_set_role(tox, state->group_number, peer2_id, TOX_GROUP_ROLE_USER, nullptr);
303
1
    }
304
4
    wait_for_peer_role(self, 2, TOX_GROUP_ROLE_USER);
305
4
    tox_scenario_barrier_wait(self); // Barrier 5: Everyone sees Peer 2 as User
306
307
    // Barrier 6: Founder promotes Peer 3 to Moderator
308
4
    wait_for_peer_role(self, 3, TOX_GROUP_ROLE_MODERATOR);
309
4
    tox_scenario_barrier_wait(self); // Barrier 6: Everyone sees Peer 3 as Moderator
310
311
    // Barrier 7: Moderator (Peer 1) attempts to kick/demote Founder (should fail)
312
4
    if (tox_node_get_index(self) == 1) {
313
1
        Tox_Err_Group_Kick_Peer err_kick;
314
1
        uint32_t founder_peer_id = state->peer_ids[0];
315
1
        tox_group_kick_peer(tox, state->group_number, founder_peer_id, &err_kick);
316
1
        ck_assert(err_kick != TOX_ERR_GROUP_KICK_PEER_OK);
317
318
1
        Tox_Err_Group_Set_Role err_role;
319
1
        tox_group_set_role(tox, state->group_number, founder_peer_id, TOX_GROUP_ROLE_USER, &err_role);
320
1
        ck_assert(err_role != TOX_ERR_GROUP_SET_ROLE_OK);
321
1
    }
322
4
    tox_scenario_barrier_wait(self); // Barrier 7: Everyone wait
323
324
    // Barrier 8: Founder kicks Moderator (Peer 1)
325
4
    if (tox_node_get_index(self) == 1) {
326
1
        WAIT_UNTIL(state->kicked);
327
1
        tox_scenario_barrier_wait(self); // Barrier 8: Peer 1 reached (it's kicked)
328
1
        return; // Exit script
329
1
    }
330
3
    tox_scenario_barrier_wait(self); // Barrier 8: Everyone else reached
331
332
    // Barrier 9: Founder demotes Moderator (Peer 3) to User
333
3
    wait_for_peer_role(self, 3, TOX_GROUP_ROLE_USER);
334
3
    tox_scenario_barrier_wait(self); // Barrier 9: Everyone sees Peer 3 as User
335
336
3
    tox_scenario_barrier_wait(self); // Done
337
3
}
338
339
int main(int argc, char *argv[])
340
3
{
341
3
    setvbuf(stdout, nullptr, _IONBF, 0);
342
3
    setvbuf(stderr, nullptr, _IONBF, 0);
343
344
3
    ToxScenario *s = tox_scenario_new(argc, argv, 300000); // 5 virtual minutes
345
3
    ModState states[NUM_PEERS] = {0};
346
3
    ToxNode *nodes[NUM_PEERS];
347
348
3
    nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(ModState));
349
3
    static char aliases[NUM_PEERS][16];
350
13
    for (int i = 1; i < NUM_PEERS; ++i) {
351
10
        snprintf(aliases[i], sizeof(aliases[i]), "Peer%d", i);
352
10
        nodes[i] = tox_scenario_add_node(s, aliases[i], peer_script, &states[i], sizeof(ModState));
353
10
    }
354
355
16
    for (int i = 0; i < NUM_PEERS; ++i) {
356
72
        for (int j = 0; j < NUM_PEERS; ++j) {
357
59
            if (i != j) {
358
46
                tox_node_bootstrap(nodes[i], nodes[j]);
359
46
                tox_node_friend_add(nodes[i], nodes[j]);
360
46
            }
361
59
        }
362
13
    }
363
364
3
    ToxScenarioStatus res = tox_scenario_run(s);
365
3
    if (res != TOX_SCENARIO_DONE) {
366
0
        tox_scenario_log(s, "Test failed with status %u", res);
367
0
        return 1;
368
0
    }
369
370
3
    tox_scenario_free(s);
371
3
    return 0;
372
3
}
373
374
#undef GROUP_NAME
375
#undef GROUP_NAME_LEN
376
#undef NUM_PEERS