mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:00:24 +08:00
109 lines
3.1 KiB
JavaScript
109 lines
3.1 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { createHash } = require('node:crypto');
|
|
const fs = require('node:fs/promises');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { afterEach, test } = require('node:test');
|
|
|
|
let nodeSqlite;
|
|
try {
|
|
nodeSqlite = require('node:sqlite');
|
|
} catch {
|
|
nodeSqlite = undefined;
|
|
}
|
|
|
|
const temporaryDirectories = [];
|
|
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
|
|
async function temporaryRoot() {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql3-backup-'));
|
|
temporaryDirectories.push(root);
|
|
return root;
|
|
}
|
|
|
|
function sha256(value) {
|
|
return createHash('sha256').update(value).digest('hex');
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryDirectories
|
|
.splice(0)
|
|
.map((directory) => fs.rm(directory, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
test(
|
|
'Node 24 Online Backup produces an integrity-checked restorable snapshot',
|
|
{ skip: nodeMajor < 24 },
|
|
async () => {
|
|
assert.equal(typeof nodeSqlite?.backup, 'function');
|
|
const root = await temporaryRoot();
|
|
const sourcePath = path.join(root, 'source.sqlite');
|
|
const backupPath = path.join(root, 'backup.sqlite');
|
|
const restoredPath = path.join(root, 'restored.sqlite');
|
|
const source = new nodeSqlite.DatabaseSync(sourcePath);
|
|
source.exec(
|
|
'PRAGMA journal_mode=WAL; CREATE TABLE facts(id INTEGER PRIMARY KEY, value TEXT NOT NULL);',
|
|
);
|
|
const insert = source.prepare('INSERT INTO facts(value) VALUES (?)');
|
|
for (let index = 0; index < 2_000; index += 1) {
|
|
insert.run('fact-' + index + '-' + 'x'.repeat(128));
|
|
}
|
|
|
|
let progressCalls = 0;
|
|
let insertedDuringBackup = false;
|
|
const pages = await nodeSqlite.backup(source, backupPath, {
|
|
rate: 1,
|
|
progress() {
|
|
progressCalls += 1;
|
|
if (!insertedDuringBackup) {
|
|
insertedDuringBackup = true;
|
|
insert.run('inserted-during-online-backup');
|
|
}
|
|
},
|
|
});
|
|
source.close();
|
|
|
|
assert.ok(pages > 0);
|
|
assert.ok(progressCalls > 0);
|
|
assert.equal(insertedDuringBackup, true);
|
|
const backup = new nodeSqlite.DatabaseSync(backupPath, {
|
|
readOnly: true,
|
|
});
|
|
assert.equal(
|
|
backup.prepare('PRAGMA integrity_check').get().integrity_check,
|
|
'ok',
|
|
);
|
|
assert.equal(
|
|
backup
|
|
.prepare(
|
|
"SELECT COUNT(*) AS count FROM facts WHERE value = 'inserted-during-online-backup'",
|
|
)
|
|
.get().count,
|
|
1,
|
|
);
|
|
const expectedCount = backup
|
|
.prepare('SELECT COUNT(*) AS count FROM facts')
|
|
.get().count;
|
|
backup.close();
|
|
|
|
await fs.copyFile(backupPath, restoredPath);
|
|
const restoredBytes = await fs.readFile(restoredPath);
|
|
const backupBytes = await fs.readFile(backupPath);
|
|
assert.equal(sha256(restoredBytes), sha256(backupBytes));
|
|
const restored = new nodeSqlite.DatabaseSync(restoredPath, {
|
|
readOnly: true,
|
|
});
|
|
assert.equal(
|
|
restored.prepare('PRAGMA quick_check').get().quick_check,
|
|
'ok',
|
|
);
|
|
assert.equal(
|
|
restored.prepare('SELECT COUNT(*) AS count FROM facts').get().count,
|
|
expectedCount,
|
|
);
|
|
restored.close();
|
|
},
|
|
);
|