release: Multi-Hoster-Upload 2.1.7
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const SourceDeleteJournal = require('../lib/source-delete-journal');
|
||||
|
||||
test('restores a staged source after an interrupted deletion', async (t) => {
|
||||
const directory = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'mhu-delete-journal-'));
|
||||
t.after(() => fs.promises.rm(directory, { recursive: true, force: true }));
|
||||
const file = path.join(directory, 'source.bin');
|
||||
const stagedFile = path.join(directory, '.source.bin.mhu-delete-test');
|
||||
const journal = new SourceDeleteJournal(path.join(directory, 'journal.jsonl'));
|
||||
await fs.promises.writeFile(file, 'source');
|
||||
await journal.plan({ token: 'cleanup-1', file, stagedFile });
|
||||
await fs.promises.rename(file, stagedFile);
|
||||
|
||||
const outcomes = await journal.recover();
|
||||
|
||||
assert.equal(await fs.promises.readFile(file, 'utf-8'), 'source');
|
||||
assert.equal(outcomes[0].outcome, 'restored');
|
||||
assert.deepEqual(await journal.recover(), []);
|
||||
});
|
||||
|
||||
test('clears a completed deletion record when neither source nor stage remains', async (t) => {
|
||||
const directory = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'mhu-delete-journal-'));
|
||||
t.after(() => fs.promises.rm(directory, { recursive: true, force: true }));
|
||||
const journal = new SourceDeleteJournal(path.join(directory, 'journal.jsonl'));
|
||||
await journal.plan({ token: 'cleanup-2', file: path.join(directory, 'source.bin'), stagedFile: path.join(directory, '.staged') });
|
||||
|
||||
const outcomes = await journal.recover();
|
||||
|
||||
assert.equal(outcomes[0].outcome, 'cleared');
|
||||
assert.deepEqual(await journal.recover(), []);
|
||||
});
|
||||
|
||||
test('continues recovery after one malformed active entry', async (t) => {
|
||||
const directory = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'mhu-delete-journal-'));
|
||||
t.after(() => fs.promises.rm(directory, { recursive: true, force: true }));
|
||||
const journal = new SourceDeleteJournal(path.join(directory, 'journal.jsonl'));
|
||||
const file = path.join(directory, 'source.bin');
|
||||
const stagedFile = path.join(directory, '.source.bin.mhu-delete-test');
|
||||
await journal.plan({ token: 'broken', file: null, stagedFile: null });
|
||||
await fs.promises.writeFile(stagedFile, 'source');
|
||||
await journal.plan({ token: 'valid', file, stagedFile });
|
||||
|
||||
const outcomes = await journal.recover();
|
||||
|
||||
assert.equal(outcomes.find((entry) => entry.token === 'broken').outcome, 'recovery-failed');
|
||||
assert.equal(outcomes.find((entry) => entry.token === 'valid').outcome, 'restored');
|
||||
assert.equal(await fs.promises.readFile(file, 'utf-8'), 'source');
|
||||
});
|
||||
|
||||
test('retries transient Windows rename locks during recovery', async (t) => {
|
||||
const directory = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'mhu-delete-journal-'));
|
||||
t.after(() => fs.promises.rm(directory, { recursive: true, force: true }));
|
||||
const file = path.join(directory, 'source.bin');
|
||||
const stagedFile = path.join(directory, '.source.bin.mhu-delete-test');
|
||||
const journalPath = path.join(directory, 'journal.jsonl');
|
||||
let renameCalls = 0;
|
||||
const waits = [];
|
||||
const injectedFs = {
|
||||
promises: {
|
||||
mkdir: (...args) => fs.promises.mkdir(...args),
|
||||
open: (...args) => fs.promises.open(...args),
|
||||
readFile: (...args) => fs.promises.readFile(...args),
|
||||
lstat: (...args) => fs.promises.lstat(...args),
|
||||
rename: async (...args) => {
|
||||
renameCalls += 1;
|
||||
if (renameCalls < 3) throw Object.assign(new Error('locked'), { code: 'EBUSY' });
|
||||
return fs.promises.rename(...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
const writer = new SourceDeleteJournal(journalPath);
|
||||
await fs.promises.writeFile(stagedFile, 'source');
|
||||
await writer.plan({ token: 'cleanup-retry', file, stagedFile });
|
||||
const journal = new SourceDeleteJournal(journalPath, null, { fs: injectedFs, wait: async (delay) => waits.push(delay) });
|
||||
|
||||
const outcomes = await journal.recover();
|
||||
|
||||
assert.equal(outcomes[0].outcome, 'restored');
|
||||
assert.equal(renameCalls, 3);
|
||||
assert.deepEqual(waits, [100, 250]);
|
||||
});
|
||||
Reference in New Issue
Block a user