mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
require('ts-node/register/transpile-only');
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
WorkerExecutionOfferInboxLifecycle,
|
||||
WorkerExecutionOfferInboxRecoveryIncompleteError,
|
||||
WorkerExecutionOfferInboxStartupIncompleteError,
|
||||
} = require('../../back/runtime/application/workerExecutionOfferInboxLifecycle');
|
||||
|
||||
const START = 1_760_600_000_000;
|
||||
|
||||
function session() {
|
||||
return {
|
||||
id: 'worker-edge',
|
||||
sessionId: '019f8000-0000-7000-8000-000000000001',
|
||||
generation: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function audit(status) {
|
||||
return {
|
||||
status,
|
||||
observedAtMs: START,
|
||||
pagesScanned: 1,
|
||||
recordsScanned: 0,
|
||||
counts: {},
|
||||
entries: [],
|
||||
};
|
||||
}
|
||||
|
||||
function fixture(status = 'ready', releaseResult = 'released', recovery) {
|
||||
const calls = [];
|
||||
const ownership = {
|
||||
ownershipState() {
|
||||
return 'owned';
|
||||
},
|
||||
async acquireOwnership() {
|
||||
calls.push('acquire');
|
||||
return 'acquired';
|
||||
},
|
||||
async releaseOwnership() {
|
||||
calls.push('release');
|
||||
return releaseResult;
|
||||
},
|
||||
};
|
||||
const auditor = {
|
||||
async audit() {
|
||||
calls.push('audit');
|
||||
return audit(status);
|
||||
},
|
||||
};
|
||||
return {
|
||||
calls,
|
||||
lifecycle: new WorkerExecutionOfferInboxLifecycle(
|
||||
ownership,
|
||||
auditor,
|
||||
recovery,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
test('holds one ownership lease after a complete startup audit', async () => {
|
||||
const context = fixture();
|
||||
const started = await context.lifecycle.start(session());
|
||||
assert.equal(started.status, 'ready');
|
||||
assert.strictEqual(context.lifecycle.currentAudit(), started.audit);
|
||||
assert.equal(
|
||||
(await context.lifecycle.start(session())).status,
|
||||
'already_started',
|
||||
);
|
||||
assert.deepEqual(context.calls, ['acquire', 'audit']);
|
||||
assert.equal(await context.lifecycle.stop(), 'stopped');
|
||||
assert.deepEqual(context.calls, ['acquire', 'audit', 'release']);
|
||||
assert.equal(await context.lifecycle.stop(), 'not_started');
|
||||
});
|
||||
|
||||
test('keeps ownership while execution reconciliation is required', async () => {
|
||||
const context = fixture('reconciliation_required');
|
||||
assert.equal(
|
||||
(await context.lifecycle.start(session())).status,
|
||||
'reconciliation_required',
|
||||
);
|
||||
assert.deepEqual(context.calls, ['acquire', 'audit']);
|
||||
assert.equal(await context.lifecycle.stop(), 'stopped');
|
||||
});
|
||||
|
||||
test('releases ownership when the startup scan is incomplete or fails', async () => {
|
||||
const exhausted = fixture('scan_budget_exhausted');
|
||||
await assert.rejects(
|
||||
exhausted.lifecycle.start(session()),
|
||||
WorkerExecutionOfferInboxStartupIncompleteError,
|
||||
);
|
||||
assert.deepEqual(exhausted.calls, ['acquire', 'audit', 'release']);
|
||||
|
||||
const calls = [];
|
||||
const failed = new WorkerExecutionOfferInboxLifecycle(
|
||||
{
|
||||
ownershipState() {
|
||||
return 'owned';
|
||||
},
|
||||
async acquireOwnership() {
|
||||
calls.push('acquire');
|
||||
return 'acquired';
|
||||
},
|
||||
async releaseOwnership() {
|
||||
calls.push('release');
|
||||
return 'released';
|
||||
},
|
||||
},
|
||||
{
|
||||
async audit() {
|
||||
calls.push('audit');
|
||||
throw new Error('corrupt journal page');
|
||||
},
|
||||
},
|
||||
);
|
||||
await assert.rejects(failed.start(session()), /corrupt journal page/);
|
||||
assert.deepEqual(calls, ['acquire', 'audit', 'release']);
|
||||
});
|
||||
|
||||
test('reports a compromised owner instead of claiming a clean stop', async () => {
|
||||
const context = fixture('ready', 'compromised');
|
||||
await context.lifecycle.start(session());
|
||||
assert.equal(await context.lifecycle.stop(), 'ownership_compromised');
|
||||
});
|
||||
|
||||
test('runs bounded recovery and re-audits before holding ownership', async () => {
|
||||
const recoveryCalls = [];
|
||||
const context = fixture('reconciliation_required', 'released', {
|
||||
async recover(value) {
|
||||
recoveryCalls.push(value.status);
|
||||
return {
|
||||
status: 'recovered',
|
||||
actionsPlanned: 1,
|
||||
actionsAttempted: 1,
|
||||
entries: [],
|
||||
};
|
||||
},
|
||||
});
|
||||
const started = await context.lifecycle.start(session());
|
||||
assert.equal(started.status, 'reconciliation_required');
|
||||
assert.equal(started.recovery.status, 'recovered');
|
||||
assert.deepEqual(context.calls, ['acquire', 'audit', 'audit']);
|
||||
assert.deepEqual(recoveryCalls, ['reconciliation_required']);
|
||||
assert.equal(
|
||||
(await context.lifecycle.start(session())).recovery.status,
|
||||
'recovered',
|
||||
);
|
||||
await context.lifecycle.stop();
|
||||
});
|
||||
|
||||
test('releases ownership when the recovery action budget is exhausted', async () => {
|
||||
const context = fixture('reconciliation_required', 'released', {
|
||||
async recover() {
|
||||
return {
|
||||
status: 'action_budget_exhausted',
|
||||
actionsPlanned: 65,
|
||||
actionsAttempted: 0,
|
||||
entries: [],
|
||||
};
|
||||
},
|
||||
});
|
||||
await assert.rejects(
|
||||
context.lifecycle.start(session()),
|
||||
WorkerExecutionOfferInboxRecoveryIncompleteError,
|
||||
);
|
||||
assert.deepEqual(context.calls, ['acquire', 'audit', 'release']);
|
||||
});
|
||||
Reference in New Issue
Block a user