Files
Haven/test/selfDmBackfill.test.js

93 lines
3.9 KiB
JavaScript

'use strict';
// Which DMs count as notes to self after an upgrade. 4.19.0 marked every one
// member DM whose member started it, which also caught an old 1:1 DM whose
// partner left. A DM someone else wrote in is never notes to self.
//
// node --test test/selfDmBackfill.test.js
const assert = require('node:assert/strict');
const test = require('node:test');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const Database = require('better-sqlite3');
const DATA = fs.mkdtempSync(path.join(os.tmpdir(), 'haven-self-dm-'));
// Haven starting up on this data folder (its own process, the way a server
// restart opens the database again).
const start = () => execFileSync(process.execPath, ['-e', "require('./src/database').initDatabase().close()"], {
cwd: path.join(__dirname, '..'),
env: { ...process.env, HAVEN_DATA_DIR: DATA },
stdio: 'pipe',
});
start();
const DB_FILE = fs.readdirSync(DATA).find(f => f.endsWith('.db'));
let db = new Database(path.join(DATA, DB_FILE));
const reopen = () => { db.close(); start(); db = new Database(path.join(DATA, DB_FILE)); };
const user = (name) => db.prepare("INSERT INTO users (username, password_hash) VALUES (?, 'x')").run(name).lastInsertRowid;
const alice = user('sd-alice'), bob = user('sd-bob'), gone = user('sd-gone');
let n = 0;
const dm = (creator, members) => {
const id = db.prepare('INSERT INTO channels (name, code, created_by, is_dm) VALUES (?, ?, ?, 1)')
.run('DM', `sd${String(++n).padStart(6, '0')}`, creator).lastInsertRowid;
for (const m of members) db.prepare('INSERT INTO channel_members (channel_id, user_id) VALUES (?, ?)').run(id, m);
return id;
};
const say = (ch, uid) => db.prepare("INSERT INTO messages (channel_id, user_id, content) VALUES (?, ?, 'hi')").run(ch, uid);
const isSelf = (id) => db.prepare('SELECT is_self_dm FROM channels WHERE id = ?').get(id).is_self_dm;
const forget = () => db.prepare("DELETE FROM server_settings WHERE key = 'self_dm_backfill_checked'").run();
// The channels a database from each version would hold.
function scenario() {
const notes = dm(alice, [alice]);
say(notes, alice);
const emptyNotes = dm(bob, [bob]);
const partnerLeft = dm(alice, [alice]);
say(partnerLeft, alice); say(partnerLeft, bob);
const partnerDeleted = dm(alice, [alice]);
say(partnerDeleted, gone);
db.prepare('UPDATE messages SET user_id = NULL WHERE user_id = ?').run(gone);
const full = dm(alice, [alice, bob]);
say(full, bob);
return { notes, emptyNotes, partnerLeft, partnerDeleted, full };
}
test.after(() => {
db.close();
fs.rmSync(DATA, { recursive: true, force: true, maxRetries: 5 });
});
test('upgrading from 4.18 (no is_self_dm column) only marks real notes to self', () => {
db.exec('ALTER TABLE channels DROP COLUMN is_self_dm');
forget();
const c = scenario();
reopen();
assert.equal(isSelf(c.notes), 1);
assert.equal(isSelf(c.emptyNotes), 1);
assert.equal(isSelf(c.partnerLeft), 0, 'the partner wrote in it');
assert.equal(isSelf(c.partnerDeleted), 0, 'a deleted account wrote in it');
assert.equal(isSelf(c.full), 0);
assert.ok(db.prepare("SELECT 1 FROM server_settings WHERE key = 'self_dm_backfill_checked'").get());
});
test('a database 4.19.0 already backfilled is corrected once', () => {
forget();
const c = scenario();
// What 4.19.0 wrote: every one member DM its member started.
db.prepare('UPDATE channels SET is_self_dm = 1 WHERE id IN (?, ?, ?, ?)').run(c.notes, c.emptyNotes, c.partnerLeft, c.partnerDeleted);
reopen();
assert.equal(isSelf(c.notes), 1);
assert.equal(isSelf(c.emptyNotes), 1);
assert.equal(isSelf(c.partnerLeft), 0);
assert.equal(isSelf(c.partnerDeleted), 0);
// Once only: the check does not run on every start.
db.prepare('UPDATE channels SET is_self_dm = 1 WHERE id = ?').run(c.partnerLeft);
reopen();
assert.equal(isSelf(c.partnerLeft), 1);
});