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.
114 lines
3.6 KiB
JavaScript
114 lines
3.6 KiB
JavaScript
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();
|
|
const first = gate.acquire();
|
|
const second = gate.acquire();
|
|
|
|
const drain = gate.sealAndDrain();
|
|
let drained = false;
|
|
drain.then(() => {
|
|
drained = true;
|
|
});
|
|
|
|
assert.equal(gate.acquire(), null);
|
|
assert.equal(first.isOpen(), true);
|
|
assert.equal(second.isOpen(), true);
|
|
|
|
first.finish();
|
|
await Promise.resolve();
|
|
assert.equal(drained, false);
|
|
|
|
second.finish();
|
|
assert.equal(await drain, true);
|
|
assert.equal(drained, true);
|
|
});
|
|
|
|
it('reports no active mutation when sealing an idle gate', async () => {
|
|
const gate = createBatchMutationGate();
|
|
|
|
assert.equal(await gate.sealAndDrain(), false);
|
|
assert.equal(gate.acquire(), null);
|
|
});
|
|
|
|
it('returns the same drain promise and preserves the first seal snapshot', async () => {
|
|
const gate = createBatchMutationGate();
|
|
const lease = gate.acquire();
|
|
|
|
const firstDrain = gate.sealAndDrain();
|
|
lease.finish();
|
|
const secondDrain = gate.sealAndDrain();
|
|
|
|
assert.equal(secondDrain, firstDrain);
|
|
assert.equal(await firstDrain, true);
|
|
assert.equal(await gate.sealAndDrain(), true);
|
|
});
|
|
|
|
it('makes lease completion idempotent without affecting other leases', async () => {
|
|
const gate = createBatchMutationGate();
|
|
const first = gate.acquire();
|
|
const second = gate.acquire();
|
|
const drain = gate.sealAndDrain();
|
|
|
|
assert.equal(first.finish(), true);
|
|
assert.equal(first.finish(), false);
|
|
assert.equal(first.isOpen(), false);
|
|
assert.equal(second.isOpen(), true);
|
|
|
|
let drained = false;
|
|
drain.then(() => {
|
|
drained = true;
|
|
});
|
|
await Promise.resolve();
|
|
assert.equal(drained, false);
|
|
|
|
assert.equal(second.finish(), true);
|
|
assert.equal(await drain, true);
|
|
});
|
|
|
|
it('does not let a rejected caller block drain when the lease finishes in finally', async () => {
|
|
const gate = createBatchMutationGate();
|
|
const lease = gate.acquire();
|
|
const caller = (async () => {
|
|
try {
|
|
await Promise.reject(new Error('audit failed'));
|
|
} finally {
|
|
lease.finish();
|
|
}
|
|
})();
|
|
const drain = gate.sealAndDrain();
|
|
|
|
await assert.rejects(caller, /audit failed/);
|
|
assert.equal(await drain, true);
|
|
assert.equal(lease.isOpen(), false);
|
|
});
|
|
|
|
it('does not count a lease finished before sealing as active at seal', async () => {
|
|
const gate = createBatchMutationGate();
|
|
const lease = gate.acquire();
|
|
|
|
lease.finish();
|
|
|
|
assert.equal(await gate.sealAndDrain(), false);
|
|
});
|
|
});
|