Make document deletion durable before physical file cleanup

This commit is contained in:
2026-09-06 10:44:47 +03:00
parent fa56e0fe83
commit 3b40061d96
5 changed files with 81 additions and 13 deletions
+30 -2
View File
@@ -1,6 +1,6 @@
import { test, after } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { mkdtempSync, rmSync, existsSync, readFileSync, unlinkSync, mkdirSync, rmdirSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
@@ -15,7 +15,7 @@ after(() => rmSync(dir, { recursive: true, force: true }));
test('old volume migrates before indexing, preserves files and migration is repeatable', () => {
for (let i = 0; i < 2; i++) {
const db = store.openDb();
assert.equal(db.prepare('SELECT COUNT(*) n FROM schema_migrations').get().n, 4);
assert.equal(db.prepare('SELECT COUNT(*) n FROM schema_migrations').get().n, 5);
assert.ok(db.prepare('PRAGMA table_info(belgeler)').all().some(c => c.name === 'ikn'));
db.close();
}
@@ -69,3 +69,31 @@ test('API key quota is transactional and revoked slots can be reused', () => {
for(const ad of [{},[],42,'x'.repeat(121)])assert.throws(()=>store.createApiKey('invalid-name@example.test',ad));
assert.deepEqual(store.listApiKeys('invalid-name@example.test'),[]);
});
test('failed document deletion leaves the original bytes and metadata intact',()=>{
const email='delete-rollback@example.test', bytes=Buffer.from('Korunması gereken belge');
const id=store.saveDocumentFile(email,{ad:'rollback.txt'},bytes);
const file=join(store.BELGE_DIR,store.getDocument(email,id).dosya);
const db=store.openDb();
db.exec("CREATE TRIGGER reject_doc_delete BEFORE DELETE ON belgeler BEGIN SELECT RAISE(FAIL,'delete rejected'); END;");db.close();
try {
assert.throws(()=>store.deleteDocument(email,id),/delete rejected/);
assert.deepEqual(readFileSync(file),bytes);
assert.ok(store.getDocument(email,id));
} finally {const db=store.openDb();db.exec('DROP TRIGGER reject_doc_delete');db.close();}
assert.equal(store.deleteDocument(email,id),true);
assert.equal(existsSync(file),false);
});
test('committed deletion remains inaccessible and pending physical cleanup survives reopen',()=>{
const email='cleanup@example.test';
const id=store.saveDocumentFile(email,{ad:'cleanup.txt'},Buffer.from('QA'));
const file=join(store.BELGE_DIR,store.getDocument(email,id).dosya);
unlinkSync(file);mkdirSync(file); // force unlink failure without relying on OS permissions
assert.equal(store.deleteDocument(email,id),true);
assert.equal(store.getDocument(email,id),null);
const db=store.openDb();assert.equal(db.prepare('SELECT COUNT(*) n FROM document_file_cleanup').get().n,1);db.close();
rmdirSync(file);writeFileSync(file,'QA cleanup recovered');
const reopened=store.openDb();assert.equal(reopened.prepare('SELECT COUNT(*) n FROM document_file_cleanup').get().n,0);reopened.close();
assert.equal(existsSync(file),false);
});