sqlite: Add infrastructure for migration tests

This sets up the testing infrastructure to test migrations we are
doing.
It's done on a in memory database directly, we are only interested
in the statements themselves and it's easier than to try and
inject a prepared db into the store.

We do add some dummy data though to make sure we actually execute
the things as we expect.
This commit is contained in:
Reto Brunner 2022-12-28 14:57:36 +01:00
parent 063aca948c
commit 899762cddd
2 changed files with 107 additions and 3 deletions

View file

@ -23,16 +23,21 @@ try {
); );
} }
export const currentSchemaVersion = 1520239200; type Migration = {version: number; stmts: string[]};
export const currentSchemaVersion = 1520239200; // use `new Date().getTime()`
// Desired schema, adapt to the newest version and add migrations to the array below
const schema = [ const schema = [
// Schema version #1
"CREATE TABLE IF NOT EXISTS options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))", "CREATE TABLE IF NOT EXISTS options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))",
"CREATE TABLE IF NOT EXISTS messages (network TEXT, channel TEXT, time INTEGER, type TEXT, msg TEXT)", "CREATE TABLE IF NOT EXISTS messages (network TEXT, channel TEXT, time INTEGER, type TEXT, msg TEXT)",
"CREATE INDEX IF NOT EXISTS network_channel ON messages (network, channel)", "CREATE INDEX IF NOT EXISTS network_channel ON messages (network, channel)",
"CREATE INDEX IF NOT EXISTS time ON messages (time)", "CREATE INDEX IF NOT EXISTS time ON messages (time)",
]; ];
// the migrations will be executed in an exclusive transaction as a whole
export const migrations = [];
class Deferred { class Deferred {
resolve!: () => void; resolve!: () => void;
promise: Promise<void>; promise: Promise<void>;
@ -326,4 +331,8 @@ function parseSearchRowsToMessages(id: number, rows: any[]) {
return messages; return messages;
} }
export function necessaryMigrations(since: number): Migration[] {
return migrations.filter((m) => m.version > since);
}
export default SqliteMessageStorage; export default SqliteMessageStorage;

View file

@ -5,7 +5,102 @@ import {expect} from "chai";
import util from "../util"; import util from "../util";
import Msg, {MessageType} from "../../server/models/msg"; import Msg, {MessageType} from "../../server/models/msg";
import Config from "../../server/config"; import Config from "../../server/config";
import MessageStorage, {currentSchemaVersion} from "../../server/plugins/messageStorage/sqlite"; import MessageStorage, {
currentSchemaVersion,
migrations,
necessaryMigrations,
} from "../../server/plugins/messageStorage/sqlite";
import Client from "../../server/client";
import sqlite3 from "sqlite3";
const orig_schema = [
// Schema version #1
// DO NOT CHANGE THIS IN ANY WAY, it's needed to properly test migrations
"CREATE TABLE IF NOT EXISTS options (name TEXT, value TEXT, CONSTRAINT name_unique UNIQUE (name))",
"CREATE TABLE IF NOT EXISTS messages (network TEXT, channel TEXT, time INTEGER, type TEXT, msg TEXT)",
"CREATE INDEX IF NOT EXISTS network_channel ON messages (network, channel)",
"CREATE INDEX IF NOT EXISTS time ON messages (time)",
];
const v1_schema_version = 1520239200;
const v1_dummy_messages = [
{
network: "8f650427-79a2-4950-b8af-94088b61b37c",
channel: "##linux",
time: 1594845354280,
type: "message",
msg: '{"from":{"mode":"","nick":"rascul"},"text":"db on a flash drive doesn\'t sound very nice though","self":false,"highlight":false,"users":[]}',
},
{
network: "8f650427-79a2-4950-b8af-94088b61b37c",
channel: "##linux",
time: 1594845357234,
type: "message",
msg: '{"from":{"mode":"","nick":"GrandPa-G"},"text":"that\'s the point of changing to make sure.","self":false,"highlight":false,"users":[]}',
},
{
network: "8f650427-79a2-4950-b8af-94088b61b37c",
channel: "#pleroma-dev",
time: 1594845358464,
type: "message",
msg: '{"from":{"mode":"@","nick":"rinpatch"},"text":"it\'s complicated","self":false,"highlight":false,"users":[]}',
},
];
describe("SQLite migrations", function () {
let db: sqlite3.Database;
function serialize_run(stmt: string, ...params: any[]): Promise<void> {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run(stmt, params, (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
});
}
before(async function () {
db = new sqlite3.Database(":memory:");
for (const stmt of orig_schema) {
await serialize_run(stmt);
}
for (const msg of v1_dummy_messages) {
await serialize_run(
"INSERT INTO messages(network, channel, time, type, msg) VALUES(?, ?, ?, ?, ?)",
msg.network,
msg.channel,
msg.time,
msg.type,
msg.msg
);
}
});
after(function (done) {
db.close(done);
});
it("has working migrations", async function () {
const to_execute = necessaryMigrations(v1_schema_version);
expect(to_execute.length).to.eq(migrations.length);
await serialize_run("BEGIN EXCLUSIVE TRANSACTION");
for (const stmt of to_execute.map((m) => m.stmts).flat()) {
await serialize_run(stmt);
}
await serialize_run("COMMIT TRANSACTION");
});
});
describe("SQLite Message Storage", function () { describe("SQLite Message Storage", function () {
// Increase timeout due to unpredictable I/O on CI services // Increase timeout due to unpredictable I/O on CI services