100 lines
7.3 KiB
C
100 lines
7.3 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;
|
|
}
|
|
|
|
/* Отказ второго ALTER должен вернуть и первую таблицу, не потеряв очередь/квитанцию. */
|
|
static int fail_route_drop(void* arg, int action, const char* database, const char* name, const char* db, const char* trigger) {
|
|
(void)arg; (void)database; (void)db; (void)trigger;
|
|
return action == SQLITE_ALTER_TABLE && name && !strcmp(name, "dm_mail_receipts") ? 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, "CREATE TABLE dm_pending_messages(recipient INTEGER,sender INTEGER,seq INTEGER,msg BLOB,group_id INTEGER,retry_at INTEGER,"
|
|
"PRIMARY KEY(recipient,sender,seq)); INSERT INTO dm_pending_messages VALUES(22,55,7,x'1122334455',101,123);"
|
|
"CREATE TABLE dm_mail_receipts(recipient INTEGER,sender INTEGER,seq INTEGER,receipt BLOB,group_id INTEGER,"
|
|
"PRIMARY KEY(recipient,sender,seq)); INSERT INTO dm_mail_receipts VALUES(22,55,6,x'BEEF',102)");
|
|
assert(sqlite3_set_authorizer(db, fail_route_drop, NULL) == SQLITE_OK);
|
|
assert(dm_mailbox_schema_init(&inst) < 0);
|
|
assert(sqlite3_set_authorizer(db, NULL, NULL) == SQLITE_OK && sqlite3_get_autocommit(db));
|
|
assert(number(db, "SELECT group_id FROM dm_pending_messages") == 101 && number(db, "SELECT group_id FROM dm_mail_receipts") == 102);
|
|
assert(!dm_mailbox_schema_init(&inst) && !dm_mailbox_schema_init(&inst));
|
|
assert(number(db, "SELECT COUNT(*) FROM dm_pending_messages WHERE recipient=22 AND sender=55 AND seq=7"
|
|
" AND hex(msg)='1122334455' AND retry_at=123") == 1);
|
|
assert(number(db, "SELECT COUNT(*) FROM dm_mail_receipts WHERE recipient=22 AND sender=55 AND seq=6 AND hex(receipt)='BEEF'") == 1);
|
|
assert(number(db, "SELECT COUNT(*) FROM pragma_table_info('dm_pending_messages') WHERE name='group_id'") == 0);
|
|
assert(number(db, "SELECT COUNT(*) FROM pragma_table_info('dm_mail_receipts') WHERE name='group_id'") == 0);
|
|
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;
|
|
}
|