From 88035fc25d3368d2ca487335a664b22d961834ca Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 14:12:34 +0100 Subject: [PATCH] Test current group devices without changing frozen delivery targets --- tests/test_dm_groups.c | 28 ++++++++++++++++++++++++++++ tools/chatgui/src/accountlist.cpp | 1 + 2 files changed, 29 insertions(+) diff --git a/tests/test_dm_groups.c b/tests/test_dm_groups.c index 5ac441db6..00f5cba4a 100644 --- a/tests/test_dm_groups.c +++ b/tests/test_dm_groups.c @@ -10,6 +10,8 @@ #include "dm/dm_history.h" #include "dm/dm_media_receipt.h" #include "media_async/media_crypto.h" +#include "member_files/protocol.h" +#include "../lib/mem.h" #include "../lib/debug_config.h" #include "test_utils.h" #include @@ -80,6 +82,24 @@ static void seed_groups(void) { } } +static void check_members(int node, const char* conv, int own, int peer) { + uint8_t* data = NULL; size_t bytes = 0; uint64_t generation = dm_history_generation(nodes[node], conv); + assert(!dm_members_snapshot(nodes[node], conv, generation, &data, &bytes)); + assert(data && data[0] == strlen(conv) && !memcmp(data + 1, conv, data[0])); + assert(mf_get64(data + 1 + data[0]) == generation); + unsigned count = mf_get16(data + 9 + data[0]); assert(count == (unsigned)(own + peer)); + assert(bytes == 11 + data[0] + count * DM_MEMBER_WIRE_SIZE); + unsigned sides[2] = {0}; + for (unsigned i = 0; i < count; i++) { + const uint8_t* row = data + 11 + data[0] + i * DM_MEMBER_WIRE_SIZE; struct chat_member_display member; + assert(row[0] <= 1 && !chat_core_deserialize_member(row + 1, &member)); sides[row[0]]++; + assert(member.node_id && member.name[0] && member.is_self == (member.node_id == nodes[node]->node_id)); + assert(member.online == member.is_self); /* Никаких маршрутов: собственный узел online, остальные остаются в панели offline. */ + } + assert(sides[0] == (unsigned)own && sides[1] == (unsigned)peer); u_free(data); + assert(dm_members_snapshot(nodes[node], conv, generation + 1, &data, &bytes) < 0 && !data && !bytes); +} + static uint64_t start(int from, int to, enum dm_mode mode, char conv[64]) { uint64_t id = 0; assert(!dm_start(nodes[from], nodes[to]->node_id, nodes[to]->my_keys.public_key, nodes[to]->my_ed25519_pubkey, @@ -148,6 +168,7 @@ int main(void) { struct UASYNC* ua = uasync_create(); assert(ua); create_nodes(ua, root); seed_channel(); seed_groups(); char conv[64], other[64]; uint64_t group_id = start(0, 2, DM_MODE_GROUPS, conv); assert(start(0, 3, DM_MODE_GROUPS, other) == group_id); + check_members(0, conv, 2, 2); uint8_t body[DM_BODY_MAX], reply[DM_BODY_MAX], receipt[DM_RECEIPT_SIZE]; size_t len = send_body(0, conv, "from A1", body); struct dm_message_view view; assert(!dm_message_parse(body, len, &view) && view.target_count == 4 && view.seq == 1); @@ -162,6 +183,7 @@ int main(void) { } assert(scalar(1, "SELECT dir FROM dm_messages") == 1 && scalar(1, "SELECT count(*) FROM dm_outbox") == 0); assert(scalar(1, "SELECT last_out_seq FROM dm_conversations") == 0); + check_members(1, conv, 2, 2); check_members(2, conv, 2, 2); for (int author = 1; author <= 3; author++) { size_t n = send_body(author, conv, "another author", reply); assert(!dm_message_parse(reply, n, &view) && view.seq == 1); @@ -173,12 +195,17 @@ int main(void) { assert(!dm_list_messages_json(nodes[0], conv, 200, 0, json, sizeof(json), &json_len)); assert(strstr(json, "from A1") && strstr(json, "another author") && !strstr(json, "\"pending\":true")); uint64_t mixed = start(0, 4, DM_MODE_GROUPS, other); + check_members(0, other, 2, 1); assert(mixed != group_id && mixed != start(0, 4, DM_MODE_DEVICE, other)); size_t n = send_body(0, other, "device to D", reply); assert(!dm_message_parse(reply, n, &view)); assert(view.mode == DM_MODE_DEVICE && view.seq == 2 && !dm_accept_message(nodes[4], reply, n, receipt)); assert(start(0, 1, DM_MODE_DEVICE, other) == dm_derive_conv_id(nodes[0]->node_id, nodes[1]->node_id)); n = send_body(0, other, "own device", reply); assert(!dm_accept_message(nodes[1], reply, n, receipt)); group_file(conv, root); + assert(!chat_devgroup_leave(nodes[1])); + assert(!devgroup_project(nodes[0]->topo_sqlite_db, topo_node_registry_find(nodes[1]->topo_groups, nodes[1]->node_id))); + check_members(0, conv, 1, 2); + assert(scalar(0, "SELECT count(*) FROM dm_file_targets") == 3); /* Текущий состав не меняет delivery snapshot. */ /* Выход атомарен: ошибка SQL не удаляет секреты и не закрывает беседу. */ assert(!sqlite3_exec(nodes[0]->topo_sqlite_db, "CREATE TEMP TRIGGER fail_close BEFORE UPDATE OF closed ON dm_conversations" " BEGIN SELECT RAISE(ABORT,'injected closure failure'); END", NULL, NULL, NULL)); @@ -186,6 +213,7 @@ int main(void) { assert(!sqlite3_exec(nodes[0]->topo_sqlite_db, "DROP TRIGGER fail_close", NULL, NULL, NULL)); send_body(0, conv, "pending at exit", reply); assert(!chat_devgroup_leave(nodes[0])); + check_members(0, conv, 0, 0); assert(scalar(0, "SELECT count(*) FROM devgroup_local") == 0); assert(scalar(0, "SELECT count(*) FROM dm_outbox o JOIN dm_conversations c USING(conv_id) WHERE c.mode=1") == 0); assert(dm_send(nodes[0], conv, "text", (const uint8_t*)"forbidden", 9) < 0); diff --git a/tools/chatgui/src/accountlist.cpp b/tools/chatgui/src/accountlist.cpp index e602e2451..2e72047bd 100644 --- a/tools/chatgui/src/accountlist.cpp +++ b/tools/chatgui/src/accountlist.cpp @@ -247,6 +247,7 @@ void AccountList::setChannel(const QString& channelId, quint64 dmGeneration) { m_channelId = channelId; m_dmGeneration = channelId.startsWith("dm:") ? dmGeneration : 0; m_dmDevices->setVisible(m_dmGeneration != 0); + m_listView->parentWidget()->setVisible(!m_dmGeneration); // Общий splitter списка/карточки не занимает место в режиме PM. m_listView->setVisible(!m_dmGeneration); m_detailPanel->setVisible(!m_dmGeneration); if (m_dmGeneration && isVisible()) m_dmTimer->start(); else m_dmTimer->stop();