Add verified backup and restore for shared data volumes

This commit is contained in:
2026-09-06 10:52:57 +03:00
parent 8d6f3bc3ea
commit b44ec646a0
4 changed files with 171 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {DatabaseSync} from 'node:sqlite';
import {backupDataVolume,verifyDataVolume,restoreDataVolume} from '../server/backup-data-volume.mjs';
test('shared volume backup captures multiple SQLite stores, committed WAL and nested files',async()=>{
const dir=fs.mkdtempSync(join(tmpdir(),'ihaleist-volume-')),source=join(dir,'source'),backup=join(dir,'backup'),restored=join(dir,'restored');
fs.mkdirSync(source);let live;
try {
for(const name of ['kalem.sqlite','tender-history.sqlite']) {
const db=new DatabaseSync(join(source,name));db.exec('PRAGMA journal_mode=WAL;CREATE TABLE records(value TEXT);');db.prepare('INSERT INTO records VALUES (?)').run('Son Türkçe kayıt');
if(name==='kalem.sqlite')live=db;else db.close();
}
fs.mkdirSync(join(source,'nested'));fs.writeFileSync(join(source,'nested','cache.json'),'["İhale"]');
const manifest=await backupDataVolume(source,backup,{offline:true});
assert.equal(manifest.files.length,3);assert.equal(manifest.files.filter(f=>f.sqlite).length,2);
assert.equal(manifest.files.some(f=>/-wal$|-shm$/.test(f.name)),false);
await restoreDataVolume(backup,restored);
const db=new DatabaseSync(join(restored,'kalem.sqlite'));assert.equal(db.prepare('SELECT value FROM records').get().value,'Son Türkçe kayıt');db.close();
assert.equal(fs.readFileSync(join(restored,'nested','cache.json'),'utf8'),'["İhale"]');
await assert.rejects(restoreDataVolume(backup,restored),/EEXIST/);
fs.appendFileSync(join(backup,'nested','cache.json'),'changed');
await assert.rejects(verifyDataVolume(backup),/değişmiş/);
} finally {live?.close();fs.rmSync(dir,{recursive:true,force:true});}
});
test('volume backup refuses unconfirmed offline, symlinks, nested output and unlisted files',async()=>{
const dir=fs.mkdtempSync(join(tmpdir(),'ihaleist-volume-')),source=join(dir,'source'),backup=join(dir,'backup');fs.mkdirSync(source);
try {
fs.writeFileSync(join(source,'data.json'),'{}');
await assert.rejects(backupDataVolume(source,backup),/offline/);
await assert.rejects(backupDataVolume(source,join(source,'inside'),{offline:true}),/iç içe/);
fs.symlinkSync(join(source,'data.json'),join(source,'link'));
await assert.rejects(backupDataVolume(source,backup,{offline:true}),/sembolik/);
fs.unlinkSync(join(source,'link'));
await backupDataVolume(source,backup,{offline:true});
fs.writeFileSync(join(backup,'unlisted'),'extra');
await assert.rejects(verifyDataVolume(backup),/Manifest dışında/);
} finally {fs.rmSync(dir,{recursive:true,force:true});}
});