release: Multi-Hoster-Upload 2.1.7

This commit is contained in:
Sucukdeluxe
2026-08-11 16:10:14 +02:00
parent f6b20a8bc8
commit 622460d844
28 changed files with 2368 additions and 80 deletions
+85
View File
@@ -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]);
});