Harden upload lifecycle and renderer recovery
Serialize upload starts across durable audit work, drain in-flight batch additions before cleanup, and fail closed when recovery persistence is incomplete. Wire bounded renderer reload recovery with a branded localized failure surface and use merge-safe fallback log persistence.
This commit is contained in:
@@ -1,8 +1,23 @@
|
||||
const { describe, it } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { createBatchMutationGate } = require('../lib/batch-mutation-gate');
|
||||
|
||||
it('drains main-process batch mutations before source cleanup finalization', () => {
|
||||
const source = fs.readFileSync(path.join(__dirname, '..', 'main.js'), 'utf8');
|
||||
const batchDoneStart = source.indexOf("uploadManager.on('batch-done'");
|
||||
const sealAndDrain = source.indexOf('batchMutationGate.sealAndDrain()', batchDoneStart);
|
||||
const cleanupFinish = source.indexOf('sourceCleanup.finishBatch', batchDoneStart);
|
||||
assert.ok(batchDoneStart >= 0);
|
||||
assert.ok(sealAndDrain > batchDoneStart);
|
||||
assert.ok(cleanupFinish > sealAndDrain);
|
||||
assert.match(source.slice(sealAndDrain, cleanupFinish + 300), /!hadActiveBatchMutation/);
|
||||
assert.match(source, /batchMutationGate\.acquire\(\)/);
|
||||
assert.match(source, /finally\s*{\s*batchMutationLease\.finish\(\)/);
|
||||
});
|
||||
|
||||
describe('batch mutation gate', () => {
|
||||
it('keeps a seal pending until every lease active at seal has finished', async () => {
|
||||
const gate = createBatchMutationGate();
|
||||
|
||||
@@ -5,11 +5,33 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
configureStartupRenderer,
|
||||
createStartupFailureDocument,
|
||||
createStartupRecoveryCoordinator,
|
||||
createStartupWindow,
|
||||
resolveStartupLanguage
|
||||
} = require('../lib/startup-renderer');
|
||||
|
||||
test('startup failure document is a localized visible application surface', () => {
|
||||
const english = createStartupFailureDocument('en');
|
||||
const german = createStartupFailureDocument('de');
|
||||
|
||||
assert.match(english, /Multi Hoster Uploader/);
|
||||
assert.match(english, /could not load/);
|
||||
assert.match(english, /Close/);
|
||||
assert.match(german, /konnte nicht geladen werden/);
|
||||
assert.match(german, /Schließen/);
|
||||
assert.doesNotMatch(english, /Electron/);
|
||||
});
|
||||
|
||||
test('main process wires bounded startup recovery into real load and crash paths', () => {
|
||||
const source = fs.readFileSync(path.join(__dirname, '..', 'main.js'), 'utf8');
|
||||
assert.match(source, /createStartupRecoveryCoordinator/);
|
||||
assert.match(source, /startupRecoveryCoordinator\.loadInitial/);
|
||||
assert.match(source, /startupRecoveryCoordinator\.rendererCrashed/);
|
||||
assert.match(source, /startupRecoveryCoordinator\.rendererReady/);
|
||||
assert.match(source, /createStartupFailureDocument/);
|
||||
});
|
||||
|
||||
class TestBrowserWindow extends EventEmitter {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { createUploadStartReservation } = require('../lib/upload-start-reservation');
|
||||
|
||||
test('only one upload start can hold the reservation across asynchronous work', () => {
|
||||
const reservation = createUploadStartReservation();
|
||||
const first = reservation.acquire();
|
||||
|
||||
assert.ok(first);
|
||||
assert.equal(reservation.isActive(), true);
|
||||
assert.equal(reservation.acquire(), null);
|
||||
|
||||
first.release();
|
||||
const second = reservation.acquire();
|
||||
assert.ok(second);
|
||||
assert.notStrictEqual(second, first);
|
||||
});
|
||||
|
||||
test('cancelling a reserved start is visible until its owner releases it', () => {
|
||||
const reservation = createUploadStartReservation();
|
||||
const lease = reservation.acquire();
|
||||
|
||||
assert.equal(reservation.cancel(), true);
|
||||
assert.equal(lease.isCancelled(), true);
|
||||
assert.equal(reservation.acquire(), null);
|
||||
lease.release();
|
||||
assert.equal(reservation.isActive(), false);
|
||||
assert.equal(reservation.cancel(), false);
|
||||
});
|
||||
|
||||
test('stale and repeated releases cannot clear a newer reservation', () => {
|
||||
const reservation = createUploadStartReservation();
|
||||
const first = reservation.acquire();
|
||||
first.release();
|
||||
const second = reservation.acquire();
|
||||
|
||||
first.release();
|
||||
assert.equal(reservation.isActive(), true);
|
||||
assert.equal(reservation.acquire(), null);
|
||||
second.release();
|
||||
assert.equal(reservation.isActive(), false);
|
||||
});
|
||||
|
||||
test('main process reserves starts before audit and exposes cancellation during the wait', () => {
|
||||
const source = fs.readFileSync(path.join(__dirname, '..', 'main.js'), 'utf8');
|
||||
const start = source.slice(
|
||||
source.indexOf("ipcMain.handle('start-upload'"),
|
||||
source.indexOf("ipcMain.handle('cancel-selected-jobs'")
|
||||
);
|
||||
|
||||
assert.ok(start.indexOf('uploadStartReservation.acquire()') < start.indexOf('appendUploadPlanAudit(batchPlan'));
|
||||
assert.match(start, /executeReservedUploadStart\(payload, startLease\)\.finally\(\(\) => startLease\.release\(\)\)/);
|
||||
assert.match(start, /uploadStartReservation\.cancel\(\)/);
|
||||
assert.match(source, /createSettingsImportGate\(\(\) => !!uploadManager \|\| uploadStartReservation\.isActive\(\)\)/);
|
||||
});
|
||||
Reference in New Issue
Block a user