Files
qinglong/test/back/runtimeRollout.test.cjs
T

166 lines
4.5 KiB
JavaScript

require('ts-node/register/transpile-only');
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
RuntimeRolloutPolicy,
shadowOnlyRollout,
} = require('../../back/runtime/domain/runtimeRollout');
const {
MAX_RUNTIME_ROLLOUT_APPROVAL_MS,
parseRuntimeRolloutManifest,
} = require('../../back/runtime/domain/runtimeRolloutManifest');
const NOW = 1_750_000_000_000;
function enabledManifest(overrides = {}) {
return {
schemaVersion: 1,
revision: 'manual-primary-canary-1',
enabled: true,
approvedBy: 'operator:admin',
approvedAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
rollbackPlanRef: 'docs/runbooks/disable-primary.md',
rollout: {
defaultMode: 'off',
origins: { manual: 'primary' },
allowLegacyFallbackBeforeStart: false,
},
gates: {
durableCancellation: 'passed',
startupReconciliation: 'passed',
atomicLegacyProjection: 'passed',
rollbackDrill: 'passed',
edgeBudget: 'passed',
},
...overrides,
};
}
test('resolves per-origin rollout ownership without mutating caller config', () => {
const origins = { manual: 'shadow', scheduled_node: 'primary' };
const policy = new RuntimeRolloutPolicy({
defaultMode: 'off',
origins,
allowLegacyFallbackBeforeStart: true,
});
origins.manual = 'off';
assert.deepEqual(policy.decide('manual'), {
origin: 'manual',
mode: 'shadow',
owner: 'legacy',
});
assert.deepEqual(policy.decide('scheduled_node'), {
origin: 'scheduled_node',
mode: 'primary',
owner: 'runtime',
});
assert.deepEqual(policy.decide('grpc'), {
origin: 'grpc',
mode: 'off',
owner: 'legacy',
});
const snapshot = policy.snapshot();
snapshot.origins.manual = 'off';
assert.equal(policy.modeFor('manual'), 'shadow');
});
test('builds a default-off shadow-only rollout policy', () => {
const policy = shadowOnlyRollout(['manual', 'scheduled_node']);
assert.equal(policy.modeFor('manual'), 'shadow');
assert.equal(policy.modeFor('scheduled_node'), 'shadow');
assert.equal(policy.modeFor('scheduled_system'), 'off');
assert.equal(policy.snapshot().allowLegacyFallbackBeforeStart, false);
});
test('rejects unknown modes and origins at the configuration boundary', () => {
assert.throws(
() =>
new RuntimeRolloutPolicy({
defaultMode: 'invalid',
origins: {},
allowLegacyFallbackBeforeStart: false,
}),
TypeError,
);
assert.throws(
() =>
new RuntimeRolloutPolicy({
defaultMode: 'off',
origins: { unknown: 'shadow' },
allowLegacyFallbackBeforeStart: false,
}),
TypeError,
);
assert.throws(
() =>
new RuntimeRolloutPolicy({
defaultMode: 'off',
origins: { manual: 'invalid' },
allowLegacyFallbackBeforeStart: false,
}),
TypeError,
);
});
test('parses a time-bounded, manual-only rollout manifest', () => {
const decision = parseRuntimeRolloutManifest(enabledManifest(), NOW);
assert.equal(decision.manifest.enabled, true);
assert.equal(decision.manifest.revision, 'manual-primary-canary-1');
assert.equal(decision.policy.modeFor('manual'), 'primary');
assert.equal(decision.policy.modeFor('scheduled_system'), 'off');
assert.equal(
decision.policy.snapshot().allowLegacyFallbackBeforeStart,
false,
);
const disabled = parseRuntimeRolloutManifest(
{ schemaVersion: 1, revision: 'disabled-1', enabled: false },
NOW,
);
assert.equal(disabled.policy.modeFor('manual'), 'off');
});
test('rejects broad, stale, incomplete, and extensible rollout manifests', () => {
const cases = [
enabledManifest({
rollout: {
defaultMode: 'primary',
origins: { manual: 'primary' },
allowLegacyFallbackBeforeStart: false,
},
}),
enabledManifest({
rollout: {
defaultMode: 'off',
origins: { scheduled_system: 'primary' },
allowLegacyFallbackBeforeStart: false,
},
}),
enabledManifest({
expiresAtMs: NOW,
}),
enabledManifest({
expiresAtMs: NOW - 1_000 + MAX_RUNTIME_ROLLOUT_APPROVAL_MS + 1,
}),
enabledManifest({
gates: {
durableCancellation: 'passed',
startupReconciliation: 'passed',
atomicLegacyProjection: 'passed',
rollbackDrill: 'passed',
},
}),
{ ...enabledManifest(), futureField: true },
];
for (const manifest of cases) {
assert.throws(() => parseRuntimeRolloutManifest(manifest, NOW), TypeError);
}
});