Files
utun2/tests/test_dm_schema.c
T

80 lines
5.4 KiB
C

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#include "utun_instance.h"
#include "dm/dm_schema.h"
static void sql(sqlite3* db, const char* text) {
int rc = sqlite3_exec(db, text, NULL, NULL, NULL);
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_DM, "schema test: SQL failed rc=%d text=%s error=%s", rc, text, sqlite3_errmsg(db));
assert(rc == SQLITE_OK);
}
static sqlite3_int64 number(sqlite3* db, const char* text) {
sqlite3_stmt* st = NULL;
assert(sqlite3_prepare_v2(db, text, -1, &st, NULL) == SQLITE_OK && sqlite3_step(st) == SQLITE_ROW);
sqlite3_int64 value = sqlite3_column_int64(st, 0);
assert(sqlite3_step(st) == SQLITE_DONE && sqlite3_finalize(st) == SQLITE_OK);
return value;
}
static int fail_drop(void* arg, int action, const char* name, const char* column, const char* db, const char* trigger) {
(void)arg; (void)column; (void)db; (void)trigger;
return action == SQLITE_DROP_TABLE && name && !strcmp(name, "dm_messages_previous") ? SQLITE_DENY : SQLITE_OK;
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG);
struct UTUN_INSTANCE inst = {.node_id = 55};
assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK);
sqlite3* db = inst.topo_sqlite_db;
/* Снимок прежней схемы, включая неотправленное тело и локально удалённую историю. */
sql(db, "CREATE TABLE dm_conversations(conv_id TEXT PRIMARY KEY,peer_node_id INTEGER NOT NULL,peer_x25519 BLOB NOT NULL,"
"peer_ed25519 BLOB NOT NULL,peer_name TEXT,group_id INTEGER,last_out_seq INTEGER,last_in_seq INTEGER,created_at INTEGER,last_ts INTEGER);"
"INSERT INTO dm_conversations VALUES('1',22,zeroblob(32),zeroblob(32),'old peer',10,7,3,1,2);"
"INSERT INTO dm_conversations VALUES('2',33,zeroblob(32),zeroblob(32),'another peer',10,19,0,1,2);"
"CREATE TABLE dm_messages(conv_id TEXT NOT NULL,dir INTEGER NOT NULL,seq INTEGER NOT NULL,ts INTEGER NOT NULL,"
"author INTEGER NOT NULL,ct TEXT,data BLOB,sig BLOB,PRIMARY KEY(conv_id,dir,seq));"
"INSERT INTO dm_messages VALUES('1',1,7,2,55,'text',x'0102',x'0304');"
"INSERT INTO dm_messages VALUES('1',0,3,1,22,'text',x'0506',x'0708');"
"CREATE TABLE dm_deleted_messages(conv_id TEXT NOT NULL,dir INTEGER NOT NULL,seq INTEGER NOT NULL,"
"body_hash BLOB NOT NULL,media_id BLOB,media_receipt BLOB,PRIMARY KEY(conv_id,dir,seq));"
"INSERT INTO dm_deleted_messages VALUES('1',1,1,x'010203',NULL,NULL);"
"INSERT INTO dm_deleted_messages VALUES('1',0,1,x'040506',x'07',x'08');"
"CREATE TABLE dm_outbox(conv_id TEXT NOT NULL,seq INTEGER NOT NULL,body BLOB NOT NULL,retry_at INTEGER NOT NULL,PRIMARY KEY(conv_id,seq));"
"INSERT INTO dm_outbox VALUES('1',7,x'1122334455',123);");
assert(sqlite3_set_authorizer(db, fail_drop, NULL) == SQLITE_OK);
assert(dm_schema_init(&inst) < 0);
assert(sqlite3_set_authorizer(db, NULL, NULL) == SQLITE_OK);
assert(number(db, "SELECT COUNT(*) FROM pragma_table_info('dm_messages') WHERE name='body'") == 0);
assert(number(db, "SELECT COUNT(*) FROM dm_messages") == 2 && sqlite3_get_autocommit(db));
assert(!dm_schema_init(&inst));
assert(number(db, "SELECT COUNT(*) FROM dm_messages WHERE body IS NULL") == 2);
assert(number(db, "SELECT COUNT(*) FROM dm_messages WHERE author=55 AND hex(data)='0102' AND hex(sig)='0304'") == 1);
assert(number(db, "SELECT COUNT(*) FROM dm_outbox WHERE hex(body)='1122334455' AND retry_at=123") == 1);
assert(number(db, "SELECT COUNT(*) FROM dm_deleted_messages WHERE author=55 AND hex(body_hash)='010203'") == 1);
assert(number(db, "SELECT COUNT(*) FROM dm_deleted_messages WHERE author=22 AND hex(media_id)='07' AND hex(media_receipt)='08'") == 1);
assert(number(db, "SELECT COUNT(*) FROM dm_delivery WHERE author=55 AND recipient=22 AND seq=7 AND retry_at=123") == 1);
assert(number(db, "SELECT COUNT(*) FROM dm_conversations WHERE mode=0 AND local_party IS NULL AND closed=0") == 2);
sql(db, "INSERT INTO dm_messages VALUES('1',1,7,3,66,'text',x'09',x'0A',x'0B')");
assert(number(db, "SELECT COUNT(*) FROM dm_messages WHERE conv_id='1' AND dir=1 AND seq=7") == 2);
uint64_t seq = 0;
assert(!dm_sequence_next(&inst, &seq) && seq == 20);
assert(dm_sequence_take(&inst, seq) < 0);
sql(db, "BEGIN"); assert(!dm_sequence_take(&inst, seq)); sql(db, "ROLLBACK");
assert(!dm_sequence_next(&inst, &seq) && seq == 20);
sql(db, "BEGIN"); assert(!dm_sequence_take(&inst, seq)); assert(dm_sequence_take(&inst, seq) < 0); sql(db, "COMMIT");
assert(!dm_schema_init(&inst) && !dm_sequence_next(&inst, &seq) && seq == 21);
assert(number(db, "SELECT COUNT(*) FROM dm_delivery") == 1 && number(db, "SELECT COUNT(*) FROM dm_messages") == 3);
sql(db, "UPDATE dm_sequence SET value=9223372036854775807");
assert(dm_sequence_next(&inst, &seq) < 0 && seq == 21);
assert(sqlite3_close(db) == SQLITE_OK);
/* Чистая база создаётся сразу с новой идентичностью сообщения. */
assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK && !dm_schema_init(&inst));
assert(!dm_sequence_next(&inst, &seq) && seq == 1);
assert(sqlite3_close(inst.topo_sqlite_db) == SQLITE_OK);
puts("PASS DM schema: atomic migration, immutable outbox, replay fingerprints, multiple authors and durable sequence");
return 0;
}