Make document deletion durable before physical file cleanup
This commit is contained in:
@@ -22,14 +22,20 @@ test('failed document insert removes only the new upload and preserves previous
|
||||
assert.equal(store.listDocuments('a').length,1);
|
||||
});
|
||||
|
||||
test('filesystem deletion error preserves document metadata instead of reporting success',()=>{
|
||||
test('filesystem cleanup failure keeps a durable deletion task and denies document access',()=>{
|
||||
const destination=join(store.BELGE_DIR,'not-a-file');
|
||||
fs.mkdirSync(destination);
|
||||
const db=store.openDb(); db.exec('DROP TRIGGER reject_document'); db.close();
|
||||
const id=store.addDocument('a',{ad:'test',boyut:1,dosya:'not-a-file'});
|
||||
assert.throws(()=>store.deleteDocument('a',id));
|
||||
assert.ok(store.getDocument('a',id));
|
||||
assert.equal(store.deleteDocument('b',id),false);
|
||||
fs.rmdirSync(destination);
|
||||
assert.ok(store.getDocument('a',id));
|
||||
assert.equal(store.deleteDocument('a',id),true);
|
||||
assert.equal(store.getDocument('a',id),null);
|
||||
const pending=store.openDb();
|
||||
assert.ok(pending.prepare('SELECT 1 FROM document_file_cleanup WHERE dosya=?').get('not-a-file'));
|
||||
pending.close();
|
||||
fs.rmdirSync(destination);
|
||||
const recovered=store.openDb();
|
||||
assert.equal(recovered.prepare('SELECT COUNT(*) n FROM document_file_cleanup').get().n,0);
|
||||
recovered.close();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user