From 8151fc5a9c5d3daeeb376455c127546a799e7618 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 08:00:54 +0100 Subject: [PATCH] test(chat): cover startup admission, saved channel activation, and owner-close recovery --- tests/test_chat_lifecycle.c | 63 ++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/tests/test_chat_lifecycle.c b/tests/test_chat_lifecycle.c index 9bf0c678..23ede466 100644 --- a/tests/test_chat_lifecycle.c +++ b/tests/test_chat_lifecycle.c @@ -152,6 +152,57 @@ static void sql_faults(struct UTUN_INSTANCE* inst, const char* path) { printf("PASS lifecycle:%u SQL faults/retries,commit veto,context/properties/routes allocation;exact DB,callbacks,and ready preserved\n", cases); } +static sqlite3_int64 number(sqlite3* db, const char* sql) { + sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(db, sql, -1, &st, NULL) == SQLITE_OK && sqlite3_step(st) == SQLITE_ROW); + sqlite3_int64 result = sqlite3_column_int64(st, 0); assert(sqlite3_finalize(st) == SQLITE_OK); return result; +} + +static void validation(struct UTUN_INSTANCE* inst, const char* path) { + uint8_t before[32], after[32]; snapshot(reader, before); ready = 0; + assert(sqlite3_exec(inst->topo_sqlite_db, "BEGIN;CREATE TABLE lifecycle_outer(value);INSERT INTO lifecycle_outer VALUES(7)", NULL, NULL, NULL) == SQLITE_OK); + assert(chat_core_init(inst, path) < 0 && !inst->chat_core && !sqlite3_get_autocommit(inst->topo_sqlite_db)); + assert(number(inst->topo_sqlite_db, "SELECT value FROM lifecycle_outer") == 7); + assert(sqlite3_exec(inst->topo_sqlite_db, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK); + char long_path[513]; memset(long_path, 'x', sizeof(long_path)); long_path[512] = 0; + assert(chat_core_init(inst, long_path) < 0 && chat_core_init(NULL, path) < 0 && chat_core_init(inst, NULL) < 0); + chat_core_destroy(NULL); snapshot(reader, after); assert(!memcmp(before, after, 32) && !ready && !inst->chat_core); + assert(number(reader, "SELECT count(*) FROM accounts") == 0); + const char* modes[] = {"ABORT", "IGNORE"}; + for (size_t i = 0; i < 2; i++) { + char sql[256]; snprintf(sql, sizeof(sql), "CREATE TRIGGER reject_identity BEFORE INSERT ON local_identity BEGIN SELECT RAISE(%s%s);END", + modes[i], i ? "" : ",'injected identity rejection'"); + assert(sqlite3_exec(inst->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK); refused(inst, path); + assert(sqlite3_exec(inst->topo_sqlite_db, "DROP TRIGGER reject_identity", NULL, NULL, NULL) == SQLITE_OK); retry(inst, path); + } + puts("PASS lifecycle:foreign transaction preserved,NULL/oversized path rejected,ABORT/IGNORE rollback,no demonstration contacts"); +} + +static void activation(struct UTUN_INSTANCE* inst, const char* path) { + ready = 0; assert(!chat_service_start(inst) && ready == 2); + uint64_t groups[2] = {chat_core_create_channel_auto(inst, "first saved"), chat_core_create_channel_auto(inst, "second saved")}; + assert(groups[0] && groups[1]); + struct chat_msg_submit request = {.inst = inst, .data = (uint8_t*)"startup retained", .data_len = 16}; + snprintf(request.channel_id, sizeof(request.channel_id), "%llu", (unsigned long long)groups[0]); strcpy(request.content_type, "text/plain"); + assert(!chat_core_submit_message(inst, &request)); chat_service_stop(inst); + char sql[256]; snprintf(sql, sizeof(sql), "SELECT rowid FROM channels WHERE channel_id='%llu'", (unsigned long long)groups[0]); + sqlite3_int64 row = number(reader, sql); + const char* invalid[] = {"'01'", "CAST(X'313200' AS TEXT)", "'9223372036854775808'"}; + for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) { + snprintf(sql, sizeof(sql), "UPDATE channels SET channel_id=%s WHERE rowid=%lld", invalid[i], (long long)row); + assert(sqlite3_exec(inst->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK); refused(inst, path); + snprintf(sql, sizeof(sql), "UPDATE channels SET channel_id='%llu' WHERE rowid=%lld", (unsigned long long)groups[0], (long long)row); + assert(sqlite3_exec(inst->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK); + } + ready = 0; fail_sync = 2; + assert(chat_service_start(inst) < 0 && !fail_sync && !ready && !inst->chat_core && !inst->chat_started && inst->core_started); + assert(!topo_groups_find(inst->topo_groups, groups[0]) && !topo_groups_find(inst->topo_groups, groups[1])); + assert(inst->nodeinfo_cbks == preserved_callback && number(reader, "SELECT count(*) FROM channels") == 2); + snprintf(sql, sizeof(sql), "SELECT count(*) FROM msg_%llu", (unsigned long long)groups[0]); assert(number(reader, sql) == 1); + assert(!chat_service_start(inst) && ready == 2 && chat_core_count(inst, request.channel_id) == 1); + assert(topo_groups_find(inst->topo_groups, groups[0]) && topo_groups_find(inst->topo_groups, groups[1])); chat_service_stop(inst); + puts("PASS lifecycle:corrupt catalog rejected;second channel activation failure cleans all runtime,keeps data,and retries without false ready"); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); size_t baseline = u_get_allocated_count(); utun_instance_set_tun_init_enabled(0); struct UASYNC* ua = uasync_create(); assert(ua); @@ -164,10 +215,14 @@ int main(void) { assert(!utun_add_nodeinfo_cbk(inst, preserved, inst)); preserved_callback = inst->nodeinfo_cbks; unsigned denied = 0; assert(sqlite3_set_authorizer(inst->topo_sqlite_db, deny_accounts, &denied) == SQLITE_OK); refused(inst, database); assert(denied == 1); assert(sqlite3_set_authorizer(inst->topo_sqlite_db, NULL, NULL) == SQLITE_OK); - retry(inst, database); sql_faults(inst, database); - ready = 0; - assert(!chat_service_start(inst) && ready == 2); chat_service_stop(inst); + retry(inst, database); sql_faults(inst, database); validation(inst, database); activation(inst, database); + uint8_t before[32], after[32]; snapshot(reader, before); ready = 0; + target = "COMMIT /* chat init */"; fault = F_EXEC; fail_rollback = 1; inst->running = 1; + assert(chat_core_init(inst, database) < 0 && fault == F_NONE && !fail_rollback && !inst->running && !inst->chat_core); + assert(!ready && !sqlite3_get_autocommit(inst->topo_sqlite_db) && inst->nodeinfo_cbks == preserved_callback); + snapshot(reader, after); assert(!memcmp(before, after, 32)); utun_instance_destroy(inst); + snapshot(reader, after); assert(!memcmp(before, after, 32)); assert(sqlite3_close(reader) == SQLITE_OK); - utun_instance_destroy(inst); uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline); + uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline); puts("PASS lifecycle:subscription failure aborts startup without ready events;core survives and retry succeeds"); return 0; }