Fix account checkbox focus and expose per-host file size limits in GB
CI / verify (push) Waiting to run
CI / verify (push) Waiting to run
This commit is contained in:
@@ -6,6 +6,9 @@ Multi-Hoster-Upload ist eine Electron-Desktopanwendung für Windows, die große
|
|||||||
|
|
||||||
## Aktueller Zustand
|
## Aktueller Zustand
|
||||||
|
|
||||||
|
- Nach `2.1.48` lokal geändert, noch nicht veröffentlicht: Checkboxen in den Hoster-Einstellungen übernehmen beim Mausklick nicht mehr den Fokus-Schatten von Textfeldern. Tastatur-Fokusmarkierung bleibt erhalten; echter isolierter Electron-Test prüft An-/Abhaken, Tabulator, Leertaste und unveränderten Zahlenfeld-Fokus.
|
||||||
|
- Hoster-Dateigrößenlimit wird direkt in GB eingegeben (`0` unbegrenzt, `1 GB = 1024 MB`). Beide Speicherwege rechnen verlustfrei in das bestehende `maxSizeMb`-Format um; bestehende Einstellungen und Backups bleiben kompatibel. Import-Vorprüfung und Upload-Manager überspringen zu große Dateien pro Hoster, lassen die exakte Grenze und andere Hoster zu. Regressionen prüfen 5 GB, einen Byte darüber sowie Dezimalwerte. Vorab übersprungene Jobs melden korrekt 0 Versuche statt der konfigurierten Höchstzahl.
|
||||||
|
|
||||||
- IP-Erweiterung am 22.09.2026 nach gesondertem Go aktiviert: Backup-API `2.0.6` produktiv; App `2.1.48` auf beiden Plattformen veröffentlicht. Vorheriger Live-Iststand: Dienst `2.0.5`, 13 Datensätze. Sicherung `/var/backups/mhu-backup-api/20260922-pre-ip.tar.gz` enthält Daten, Konfiguration, öffentlichen Wiederherstellungsschlüssel, Dienstdefinition und bisherigen Dienststand. Archivtest erfolgreich; SHA-256 `7be0a9f4497cae20d89606e4872d41eb555fd66bf4613503e6f84e4aa5300e7f` entspricht der geschützten lokalen Kopie `server-backup-pre-ip-20260922.tar.gz` im Wiederherstellungsordner.
|
- IP-Erweiterung am 22.09.2026 nach gesondertem Go aktiviert: Backup-API `2.0.6` produktiv; App `2.1.48` auf beiden Plattformen veröffentlicht. Vorheriger Live-Iststand: Dienst `2.0.5`, 13 Datensätze. Sicherung `/var/backups/mhu-backup-api/20260922-pre-ip.tar.gz` enthält Daten, Konfiguration, öffentlichen Wiederherstellungsschlüssel, Dienstdefinition und bisherigen Dienststand. Archivtest erfolgreich; SHA-256 `7be0a9f4497cae20d89606e4872d41eb555fd66bf4613503e6f84e4aa5300e7f` entspricht der geschützten lokalen Kopie `server-backup-pre-ip-20260922.tar.gz` im Wiederherstellungsordner.
|
||||||
- Staging `staging-ip`: Alle 13 Bestandsdatensätze unverändert lesbar; Erstellen eines Version-4-Testbackups mit Herkunfts-IP, private Wiederherstellung, normaler Import und gezieltes Löschen erfolgreich. Anschließend sämtliche 13 Live-Dateien per SHA-256 mit der getesteten Kopie abgeglichen. Keine Live-Datensätze verändert.
|
- Staging `staging-ip`: Alle 13 Bestandsdatensätze unverändert lesbar; Erstellen eines Version-4-Testbackups mit Herkunfts-IP, private Wiederherstellung, normaler Import und gezieltes Löschen erfolgreich. Anschließend sämtliche 13 Live-Dateien per SHA-256 mit der getesteten Kopie abgeglichen. Keine Live-Datensätze verändert.
|
||||||
- Lokaler Build `2.1.48`: Installer 98.032.605 Bytes, Portable 97.807.985 Bytes, Produktversion jeweils `2.1.48`. Verpackte Quellmodule und Update-Manifest geprüft; keine Schlüssel oder Nutzerdaten enthalten. Englische/deutsche Release-Texte veröffentlicht. Dienstpfad `/opt/mhu-backup-api/releases/20260922-v2.0.6` aktiviert und Dienst neu gestartet. Live-Smoke bestätigt serverseitige öffentliche Herkunfts-IP, privaten Wiederherstellungsweg und normalen Import. Ausschließlich eigenen Testdatensatz danach gelöscht; alle 13 Bestandsdatensätze per SHA-256 unverändert bestätigt. Kein pauschaler Rollback auf `2.0.5`, sobald neue Version-4-Datensätze vorhanden sind; Daten müssen erhalten bleiben.
|
- Lokaler Build `2.1.48`: Installer 98.032.605 Bytes, Portable 97.807.985 Bytes, Produktversion jeweils `2.1.48`. Verpackte Quellmodule und Update-Manifest geprüft; keine Schlüssel oder Nutzerdaten enthalten. Englische/deutsche Release-Texte veröffentlicht. Dienstpfad `/opt/mhu-backup-api/releases/20260922-v2.0.6` aktiviert und Dienst neu gestartet. Live-Smoke bestätigt serverseitige öffentliche Herkunfts-IP, privaten Wiederherstellungsweg und normalen Import. Ausschließlich eigenen Testdatensatz danach gelöscht; alle 13 Bestandsdatensätze per SHA-256 unverändert bestätigt. Kein pauschaler Rollback auf `2.0.5`, sobald neue Version-4-Datensätze vorhanden sind; Daten müssen erhalten bleiben.
|
||||||
@@ -82,6 +85,8 @@ npm audit --omit=dev
|
|||||||
|
|
||||||
## Zuletzt verifiziert
|
## Zuletzt verifiziert
|
||||||
|
|
||||||
|
- Checkbox-/GB-Limit-Änderung nach `2.1.48`: 830 Haupttests und 18 Servertests erfolgreich; Lint ohne Fehler. Echter Electron-Fokustest sowie GB-Speicher-, Import- und Upload-Grenztests erfolgreich. Änderungen noch nicht als Update veröffentlicht.
|
||||||
|
|
||||||
- Release `2.1.48`: Titel, Sprachen, Veröffentlichung, jeweils vier Assets und Update-Metadaten beider Plattformen verifiziert. Alle acht Assets erneut heruntergeladen und per Größe/SHA-512 mit lokalen Dateien verglichen. Eingebauter Updater mit Version `2.1.47` erkennt `2.1.48`, übernimmt englische Release Notes und akzeptiert das Forgejo-Manifest. Release-Commit `7a49a19`, annotierter Tag `b8df102` auf beiden Remotes identisch. Anleitung auf dem Desktop aktualisiert.
|
- Release `2.1.48`: Titel, Sprachen, Veröffentlichung, jeweils vier Assets und Update-Metadaten beider Plattformen verifiziert. Alle acht Assets erneut heruntergeladen und per Größe/SHA-512 mit lokalen Dateien verglichen. Eingebauter Updater mit Version `2.1.47` erkennt `2.1.48`, übernimmt englische Release Notes und akzeptiert das Forgejo-Manifest. Release-Commit `7a49a19`, annotierter Tag `b8df102` auf beiden Remotes identisch. Anleitung auf dem Desktop aktualisiert.
|
||||||
|
|
||||||
Stand: 22.09.2026 (Produktivaktivierung und Release `2.1.46`; ältere Versionsprüfungen darunter sind historische Nachweise)
|
Stand: 22.09.2026 (Produktivaktivierung und Release `2.1.46`; ältere Versionsprüfungen darunter sind historische Nachweise)
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ class UploadManager extends EventEmitter {
|
|||||||
status,
|
status,
|
||||||
error: payload.error || null,
|
error: payload.error || null,
|
||||||
accountId: task.accountId || null,
|
accountId: task.accountId || null,
|
||||||
attempt: payload.attempt || finalAttempt,
|
attempt: payload.attempt ?? finalAttempt,
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
durationSec: Number.isFinite(payload.elapsed) ? payload.elapsed : Math.round((Date.now() - jobStartedAt) / 1000),
|
durationSec: Number.isFinite(payload.elapsed) ? payload.elapsed : Math.round((Date.now() - jobStartedAt) / 1000),
|
||||||
failureDetails: payload.failureDetails || lastFailureDetails,
|
failureDetails: payload.failureDetails || lastFailureDetails,
|
||||||
@@ -521,7 +521,7 @@ class UploadManager extends EventEmitter {
|
|||||||
error: payload.error || null,
|
error: payload.error || null,
|
||||||
failureDetails: payload.failureDetails || lastFailureDetails,
|
failureDetails: payload.failureDetails || lastFailureDetails,
|
||||||
result: payload.result || null,
|
result: payload.result || null,
|
||||||
attempt: payload.attempt || maxAttempts,
|
attempt: payload.attempt ?? maxAttempts,
|
||||||
maxAttempts
|
maxAttempts
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-3
@@ -7302,6 +7302,7 @@ async function performSaveSettings(options = {}) {
|
|||||||
const field = input.dataset.hs;
|
const field = input.dataset.hs;
|
||||||
if (input.type === 'checkbox') hs[field] = input.checked;
|
if (input.type === 'checkbox') hs[field] = input.checked;
|
||||||
else if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
|
else if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
|
||||||
|
else if (field === 'maxSizeGb') hs.maxSizeMb = Math.max(0, (Number(input.value) || 0) * 1024);
|
||||||
else hs[field] = parseInt(input.value, 10) || 0;
|
else hs[field] = parseInt(input.value, 10) || 0;
|
||||||
});
|
});
|
||||||
newHosterSettings[name] = hs;
|
newHosterSettings[name] = hs;
|
||||||
@@ -7613,6 +7614,7 @@ async function performHosterSettingsSave() {
|
|||||||
const field = input.dataset.hs;
|
const field = input.dataset.hs;
|
||||||
if (input.type === 'checkbox') hs[field] = input.checked;
|
if (input.type === 'checkbox') hs[field] = input.checked;
|
||||||
else if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
|
else if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
|
||||||
|
else if (field === 'maxSizeGb') hs.maxSizeMb = Math.max(0, (Number(input.value) || 0) * 1024);
|
||||||
else hs[field] = parseInt(input.value, 10) || 0;
|
else hs[field] = parseInt(input.value, 10) || 0;
|
||||||
});
|
});
|
||||||
newHosterSettings[name] = hs;
|
newHosterSettings[name] = hs;
|
||||||
@@ -7694,9 +7696,9 @@ function _buildHosterSettingsHtml(name) {
|
|||||||
<input id="${fieldPrefix}-interval" type="number" class="hs-input" data-hoster="${name}" data-hs="timeIntervalSec" value="${hs.timeIntervalSec ?? 0}" min="0">
|
<input id="${fieldPrefix}-interval" type="number" class="hs-input" data-hoster="${name}" data-hs="timeIntervalSec" value="${hs.timeIntervalSec ?? 0}" min="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<label for="${fieldPrefix}-max-size">Maximale Größe (MB)</label>
|
<label for="${fieldPrefix}-max-size">Dateigrößenlimit (GB)</label>
|
||||||
<input id="${fieldPrefix}-max-size" type="number" class="hs-input" data-hoster="${name}" data-hs="maxSizeMb" value="${hs.maxSizeMb ?? 0}" min="0">
|
<input id="${fieldPrefix}-max-size" type="number" class="hs-input" data-hoster="${name}" data-hs="maxSizeGb" value="${Math.max(0, (Number(hs.maxSizeMb) || 0) / 1024)}" min="0" step="any">
|
||||||
<span class="hint">0 = unbegrenzt</span>
|
<span class="hint">0 = unbegrenzt. 1 GB = 1024 MB. Größere Dateien werden für diesen Hoster übersprungen.</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<label for="${fieldPrefix}-log">Links in Log schreiben</label>
|
<label for="${fieldPrefix}-log">Links in Log schreiben</label>
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
['Maximale Geschwindigkeit (MB/s)', 'Maximum speed (MB/s)'],
|
['Maximale Geschwindigkeit (MB/s)', 'Maximum speed (MB/s)'],
|
||||||
['Neustart unter (kB/s)', 'Restart below (kB/s)'],
|
['Neustart unter (kB/s)', 'Restart below (kB/s)'],
|
||||||
['Maximale Größe (MB)', 'Maximum size (MB)'],
|
['Maximale Größe (MB)', 'Maximum size (MB)'],
|
||||||
|
['Dateigrößenlimit (GB)', 'File size limit (GB)'],
|
||||||
|
['0 = unbegrenzt. 1 GB = 1024 MB. Größere Dateien werden für diesen Hoster übersprungen.', '0 = unlimited. 1 GB = 1024 MB. Larger files are skipped for this host.'],
|
||||||
['Bildschirm und Eingabesteuerung bleiben gesperrt.', 'Screen and input control remain locked.'],
|
['Bildschirm und Eingabesteuerung bleiben gesperrt.', 'Screen and input control remain locked.'],
|
||||||
['Bindet nur an', 'Binds only to'],
|
['Bindet nur an', 'Binds only to'],
|
||||||
['Bindet an', 'Binds to'],
|
['Bindet an', 'Binds to'],
|
||||||
|
|||||||
+1
-1
@@ -3665,7 +3665,7 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.key-input:focus,
|
.key-input:focus,
|
||||||
.hs-input:focus,
|
.hs-input:not([type="checkbox"]):focus,
|
||||||
#settingsSearchInput:focus {
|
#settingsSearchInput:focus {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow: 0 0 0 2px rgba(186, 208, 252, .12);
|
box-shadow: 0 0 0 2px rgba(186, 208, 252, .12);
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ test('inspects duplicates, unavailable files, accepted files, and configured siz
|
|||||||
assert.equal(inspection.accepted[0].mtimeMs, 1787828400123);
|
assert.equal(inspection.accepted[0].mtimeMs, 1787828400123);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('5 GB host limit excludes oversized import pairs but preserves other hosts and exact boundary', () => {
|
||||||
|
const { isImportPairEligible } = require('../lib/import-preflight');
|
||||||
|
const settings = { 'doodstream.com': { maxSizeMb: 5120 }, 'voe.sx': { maxSizeMb: 0 } };
|
||||||
|
assert.equal(isImportPairEligible({ size: 5 * 1024 ** 3 }, 'doodstream.com', settings), true);
|
||||||
|
assert.equal(isImportPairEligible({ size: 5 * 1024 ** 3 + 1 }, 'doodstream.com', settings), false);
|
||||||
|
assert.equal(isImportPairEligible({ size: 5 * 1024 ** 3 + 1 }, 'voe.sx', settings), true);
|
||||||
|
});
|
||||||
|
|
||||||
test('connects the import preflight through the main process, preload, renderer, and hoster dialog', () => {
|
test('connects the import preflight through the main process, preload, renderer, and hoster dialog', () => {
|
||||||
const root = path.join(__dirname, '..');
|
const root = path.join(__dirname, '..');
|
||||||
const main = fs.readFileSync(path.join(root, 'main.js'), 'utf8');
|
const main = fs.readFileSync(path.join(root, 'main.js'), 'utf8');
|
||||||
|
|||||||
@@ -7,6 +7,81 @@ const os = require('node:os');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { createStartupWindow, resolveStartupLanguage, createStartupQuery } = require('../lib/startup-renderer');
|
const { createStartupWindow, resolveStartupLanguage, createStartupQuery } = require('../lib/startup-renderer');
|
||||||
|
|
||||||
|
test('host file-size settings display GB and save compatible MB values without truncating decimals', async () => {
|
||||||
|
const vm = require('node:vm');
|
||||||
|
const source = fs.readFileSync(path.join(__dirname, '../renderer/app.js'), 'utf8');
|
||||||
|
const saveFunction = source.slice(source.indexOf('async function performHosterSettingsSave()'), source.indexOf('\nasync function recoverSerializedSave'));
|
||||||
|
const buildFunction = source.slice(source.indexOf('function _buildHosterSettingsHtml(name)'), source.indexOf('\nfunction _buildAccountHosterGroupHtml'));
|
||||||
|
let saved;
|
||||||
|
const input = { dataset: { hs: 'maxSizeGb' }, type: 'number', value: '5' };
|
||||||
|
const context = vm.createContext({
|
||||||
|
config: { hosterSettings: { 'doodstream.com': { maxSizeMb: 5120 }, 'voe.sx': { maxSizeMb: 1024 } } },
|
||||||
|
hosterSettings: {}, HOSTERS: ['doodstream.com', 'voe.sx'],
|
||||||
|
document: { querySelectorAll: selector => selector.includes('doodstream.com') ? [input] : [] },
|
||||||
|
saveHosterSettingsTracked: async value => { saved = value; }
|
||||||
|
});
|
||||||
|
vm.runInContext(saveFunction + '\n' + buildFunction, context);
|
||||||
|
assert.match(vm.runInContext("_buildHosterSettingsHtml('doodstream.com')", context), /data-hs="maxSizeGb" value="5"/);
|
||||||
|
for (const [gb, mb] of [['5', 5120], ['2.5', 2560], ['0', 0], ['0.0009765625', 1]]) {
|
||||||
|
input.value = gb;
|
||||||
|
await vm.runInContext('performHosterSettingsSave()', context);
|
||||||
|
assert.equal(saved['doodstream.com'].maxSizeMb, mb);
|
||||||
|
assert.equal(saved['voe.sx'].maxSizeMb, 1024);
|
||||||
|
assert.equal(Object.hasOwn(saved['doodstream.com'], 'maxSizeGb'), false);
|
||||||
|
}
|
||||||
|
assert.equal((source.match(/field === 'maxSizeGb'/g) || []).length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('account checkboxes have no text-field shadow on mouse clicks and keep keyboard focus', { skip: process.platform !== 'win32' }, t => {
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'mhu-checkbox-focus-'));
|
||||||
|
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||||
|
const probe = path.join(directory, 'probe.cjs');
|
||||||
|
const result = path.join(directory, 'result.json');
|
||||||
|
fs.writeFileSync(probe, `
|
||||||
|
const { app, BrowserWindow } = require('electron');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
app.whenReady().then(async () => {
|
||||||
|
const css = fs.readFileSync(process.env.MHU_CHECKBOX_CSS, 'utf8');
|
||||||
|
const win = new BrowserWindow({ show: false, width: 500, height: 400 });
|
||||||
|
const wc = win.webContents;
|
||||||
|
await win.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent('<style>' + css + '</style><button id="before">Before</button><input id="check" class="hs-input" type="checkbox"><input id="number" class="hs-input" type="number" value="5">'));
|
||||||
|
wc.focus();
|
||||||
|
const point = await wc.executeJavaScript('(() => { const r = document.getElementById("check").getBoundingClientRect(); return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) }; })()');
|
||||||
|
for (const expected of [true, false]) {
|
||||||
|
wc.sendInputEvent({ type: 'mouseDown', button: 'left', clickCount: 1, ...point });
|
||||||
|
wc.sendInputEvent({ type: 'mouseUp', button: 'left', clickCount: 1, ...point });
|
||||||
|
const state = await wc.executeJavaScript('(() => { const c = document.getElementById("check"); const s = getComputedStyle(c); return { checked: c.checked, shadow: s.boxShadow, outline: s.outlineStyle }; })()');
|
||||||
|
assert.equal(state.checked, expected);
|
||||||
|
assert.equal(state.shadow, 'none');
|
||||||
|
assert.equal(state.outline, 'none');
|
||||||
|
}
|
||||||
|
await wc.executeJavaScript('document.getElementById("before").focus()');
|
||||||
|
wc.sendInputEvent({ type: 'keyDown', keyCode: 'Tab' });
|
||||||
|
wc.sendInputEvent({ type: 'keyUp', keyCode: 'Tab' });
|
||||||
|
const keyboard = await wc.executeJavaScript('(() => { const c = document.getElementById("check"); return { active: document.activeElement === c, visible: c.matches(":focus-visible"), outline: getComputedStyle(c).outlineStyle }; })()');
|
||||||
|
assert.equal(keyboard.active, true);
|
||||||
|
assert.equal(keyboard.visible, true);
|
||||||
|
assert.notEqual(keyboard.outline, 'none');
|
||||||
|
wc.sendInputEvent({ type: 'keyDown', keyCode: 'Space' });
|
||||||
|
wc.sendInputEvent({ type: 'keyUp', keyCode: 'Space' });
|
||||||
|
assert.equal(await wc.executeJavaScript('document.getElementById("check").checked'), true);
|
||||||
|
assert.notEqual(await wc.executeJavaScript('document.getElementById("number").focus(); getComputedStyle(document.getElementById("number")).boxShadow'), 'none');
|
||||||
|
win.destroy();
|
||||||
|
fs.writeFileSync(process.env.MHU_CHECKBOX_RESULT, JSON.stringify({ ok: true }));
|
||||||
|
app.exit(0);
|
||||||
|
}).catch(error => { fs.writeFileSync(process.env.MHU_CHECKBOX_RESULT, JSON.stringify({ error: error.message })); app.exit(1); });
|
||||||
|
`);
|
||||||
|
const execution = spawnSync(path.join(root, 'node_modules/electron/dist/electron.exe'), [probe, '--user-data-dir=' + path.join(directory, 'profile')], {
|
||||||
|
cwd: root, windowsHide: true, encoding: 'utf8', timeout: 20000,
|
||||||
|
env: { ...process.env, MHU_CHECKBOX_CSS: path.join(root, 'renderer/styles.css'), MHU_CHECKBOX_RESULT: result }
|
||||||
|
});
|
||||||
|
const data = fs.existsSync(result) ? JSON.parse(fs.readFileSync(result, 'utf8')) : {};
|
||||||
|
assert.equal(execution.status, 0, data.error || execution.stderr);
|
||||||
|
assert.equal(data.ok, true);
|
||||||
|
});
|
||||||
|
|
||||||
class TestBrowserWindow extends EventEmitter {
|
class TestBrowserWindow extends EventEmitter {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -340,6 +340,30 @@ describe('UploadManager', () => {
|
|||||||
assert.ok(!statuses.includes('uploading'), 'should not attempt upload');
|
assert.ok(!statuses.includes('uploading'), 'should not attempt upload');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('enforces a 5 GB host limit before uploads or account fallback and allows the boundary', async () => {
|
||||||
|
const limit = 5 * 1024 ** 3;
|
||||||
|
const mgr = new UploadManager({
|
||||||
|
'doodstream.com': { maxSizeMb: 5 * 1024, retries: 3, parallelCount: 1 },
|
||||||
|
'voe.sx': { maxSizeMb: 0, retries: 0, parallelCount: 1 }
|
||||||
|
});
|
||||||
|
const events = [];
|
||||||
|
mgr.on('progress', event => events.push(event));
|
||||||
|
fakeFileSize = limit + 1;
|
||||||
|
await mgr.startBatch([
|
||||||
|
{ file: '/test/too-large.mp4', hoster: 'doodstream.com', apiKey: 'key1' },
|
||||||
|
{ file: '/test/too-large.mp4', hoster: 'voe.sx', apiKey: 'key2' }
|
||||||
|
]);
|
||||||
|
assert.equal(mockUploadFile.mock.callCount(), 1);
|
||||||
|
assert.equal(mockUploadFile.mock.calls[0].arguments[0], 'voe.sx');
|
||||||
|
const skipped = events.find(event => event.hoster === 'doodstream.com' && event.status === 'skipped');
|
||||||
|
assert.ok(skipped);
|
||||||
|
assert.equal(skipped.attempt, 0);
|
||||||
|
fakeFileSize = limit;
|
||||||
|
await mgr.startBatch([{ file: '/test/exactly-five-gb.mp4', hoster: 'doodstream.com', apiKey: 'key1' }]);
|
||||||
|
assert.equal(mockUploadFile.mock.callCount(), 2);
|
||||||
|
assert.ok(events.some(event => event.hoster === 'doodstream.com' && event.status === 'done'));
|
||||||
|
});
|
||||||
|
|
||||||
it('per-hoster semaphore limits concurrency', async () => {
|
it('per-hoster semaphore limits concurrency', async () => {
|
||||||
let concurrent = 0;
|
let concurrent = 0;
|
||||||
let maxConcurrent = 0;
|
let maxConcurrent = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user