release: v2.1.12 reliability and security hardening
CI / verify (push) Waiting to run

Verify update artifacts with exact metadata and SHA-512, require host-confirmed upload completion before cleanup, harden credentials and backups, improve queue recovery and skipped-state reporting, expand Windows path coverage, and add CI packaging checks.
This commit is contained in:
Sucukdeluxe
2026-08-11 22:36:21 +02:00
parent 2c124c848c
commit 2ba0106ef0
47 changed files with 2069 additions and 1192 deletions
+48 -1
View File
@@ -4,7 +4,8 @@ const {
summarizePerHoster,
classifyErrorCategory,
summarizeBatchErrors,
isRetryableCategory
isRetryableCategory,
mergeSkippedIntoSummary
} = require('../lib/stats');
function makeBatch(timestamp, results) {
@@ -64,6 +65,17 @@ test('summarizePerHoster handles empty / malformed input', () => {
assert.deepStrictEqual(summarizePerHoster([{ id: 'x', files: null }]), {});
});
test('summarizePerHoster reports skipped uploads without lowering the host success rate', () => {
const history = [makeBatch(1, [
{ hoster: 'voe.sx', status: 'done' },
{ hoster: 'voe.sx', status: 'error', error: 'x' },
{ hoster: 'voe.sx', status: 'skipped', error: 'Datei zu groß' }
])];
const summary = summarizePerHoster(history)['voe.sx'];
assert.deepStrictEqual({ ok: summary.ok, fail: summary.fail, skipped: summary.skipped, total: summary.total }, { ok: 1, fail: 1, skipped: 1, total: 3 });
assert.strictEqual(summary.rate, 0.5);
});
test('classifyErrorCategory: file-rejected phrases', () => {
assert.strictEqual(classifyErrorCategory('Byse lehnte Datei ab: Not video file format'), 'file-rejected');
assert.strictEqual(classifyErrorCategory('Duplicate file already exists'), 'file-rejected');
@@ -122,6 +134,41 @@ test('summarizeBatchErrors buckets results by category', () => {
assert.strictEqual(buckets['account-error'].length, 0);
});
test('summarizeBatchErrors excludes skipped results from error and retry buckets', () => {
const buckets = summarizeBatchErrors({
files: [{ name: 'a.mp4', results: [{ hoster: 'voe.sx', status: 'skipped', error: 'Kein gültiger Account' }] }]
});
assert.strictEqual(Object.values(buckets).flat().length, 0);
});
test('mergeSkippedIntoSummary adds skipped jobs to totals and history files', () => {
const summary = {
id: 'batch-1',
timestamp: '2026-08-11T12:00:00.000Z',
total: 1,
succeeded: 1,
failed: 0,
skipped: 0,
files: [{ name: 'ok.mp4', size: 5, results: [{ jobId: 'ok', hoster: 'voe.sx', status: 'done' }] }]
};
const merged = mergeSkippedIntoSummary(summary, [{
jobId: 'skip',
file: 'C:\\incoming\\skip.mp4',
fileName: 'skip.mp4',
hoster: 'byse.sx',
reason: 'Kein gültiger Account'
}]);
assert.strictEqual(merged.total, 2);
assert.strictEqual(merged.succeeded, 1);
assert.strictEqual(merged.failed, 0);
assert.strictEqual(merged.skipped, 1);
assert.deepStrictEqual(merged.files[1], {
name: 'skip.mp4',
size: 0,
results: [{ jobId: 'skip', hoster: 'byse.sx', status: 'skipped', error: 'Kein gültiger Account' }]
});
});
test('isRetryableCategory: only transient + network + unknown retry-worthy', () => {
assert.strictEqual(isRetryableCategory('hoster-transient'), true);
assert.strictEqual(isRetryableCategory('network'), true);