mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:21:50 +08:00
188 lines
5.5 KiB
JavaScript
188 lines
5.5 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
CLUSTER_LEGACY_ENV_MIGRATION_PLAN_SCHEMA,
|
|
MAX_CLUSTER_LEGACY_ENV_EFFECTIVE_BYTES,
|
|
MAX_CLUSTER_LEGACY_ENV_EFFECTIVE_BINDINGS,
|
|
MAX_CLUSTER_LEGACY_ENV_SOURCE_ROWS,
|
|
MAX_CLUSTER_LEGACY_ENV_TASKS,
|
|
MAX_CLUSTER_LEGACY_ENV_TRIGGERS,
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
clusterLegacyEnvMigrationPlanMatchesIntent,
|
|
createClusterLegacyEnvMigrationPlan,
|
|
normalizeClusterLegacyEnvMigrationPlan,
|
|
normalizeClusterLegacyEnvMigrationPlanIntent,
|
|
} = require('@qinglong/runtime-core/cluster-legacy-env-migration-plan');
|
|
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
|
|
|
|
const DIGESTS = Object.freeze({
|
|
reconciliationBundleDigest: '1'.repeat(64),
|
|
decisionDigest: '2'.repeat(64),
|
|
candidateSetDigest: '3'.repeat(64),
|
|
taskRevisionSetDigest: '4'.repeat(64),
|
|
triggerRevisionSetDigest: '5'.repeat(64),
|
|
});
|
|
|
|
function intent(overrides = {}) {
|
|
const projectId = overrides.projectId ?? 'project-a';
|
|
return {
|
|
planId: 'legacy-env-plan-a',
|
|
mutationId: 'legacy-env-mutation-a',
|
|
projectId,
|
|
source: {
|
|
reconciliationBundleDigest: DIGESTS.reconciliationBundleDigest,
|
|
decisionDigest: DIGESTS.decisionDigest,
|
|
candidateSetDigest: DIGESTS.candidateSetDigest,
|
|
sourceRowCount: 4,
|
|
activeRowCount: 3,
|
|
disabledRowCount: 1,
|
|
effectiveBindingCount: 2,
|
|
},
|
|
target: {
|
|
secretRef: createSecretRef({
|
|
projectId,
|
|
name: 'legacy-env-bundle',
|
|
version: 7,
|
|
}),
|
|
taskRevisionSetDigest: DIGESTS.taskRevisionSetDigest,
|
|
triggerRevisionSetDigest: DIGESTS.triggerRevisionSetDigest,
|
|
taskCount: 2,
|
|
triggerCount: 3,
|
|
totalEffectiveBytes: 1024,
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('creates one frozen content-free migration plan with a stable digest', () => {
|
|
const value = createClusterLegacyEnvMigrationPlan(intent(), 12_345);
|
|
assert.equal(value.schema, CLUSTER_LEGACY_ENV_MIGRATION_PLAN_SCHEMA);
|
|
assert.match(value.planDigest, /^[0-9a-f]{64}$/);
|
|
assert.equal(Object.isFrozen(value), true);
|
|
assert.equal(Object.isFrozen(value.source), true);
|
|
assert.equal(Object.isFrozen(value.target), true);
|
|
assert.deepEqual(normalizeClusterLegacyEnvMigrationPlan(value), value);
|
|
assert.equal(
|
|
clusterLegacyEnvMigrationPlanMatchesIntent(value, intent()),
|
|
true,
|
|
);
|
|
assert.deepEqual(Object.keys(value).sort(), [
|
|
'mutationId',
|
|
'planDigest',
|
|
'planId',
|
|
'plannedAtMs',
|
|
'projectId',
|
|
'schema',
|
|
'source',
|
|
'target',
|
|
]);
|
|
assert.equal(JSON.stringify(value).includes('legacy-env-bundle'), false);
|
|
});
|
|
|
|
test('requires a canonical version-pinned SecretRef in the same Project', () => {
|
|
const unversioned = intent();
|
|
unversioned.target.secretRef = createSecretRef({
|
|
projectId: unversioned.projectId,
|
|
name: 'legacy-env-bundle',
|
|
});
|
|
assert.throws(
|
|
() => normalizeClusterLegacyEnvMigrationPlanIntent(unversioned),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
|
|
const crossProject = intent();
|
|
crossProject.target.secretRef = createSecretRef({
|
|
projectId: 'project-b',
|
|
name: 'legacy-env-bundle',
|
|
version: 1,
|
|
});
|
|
assert.throws(
|
|
() => normalizeClusterLegacyEnvMigrationPlanIntent(crossProject),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
});
|
|
|
|
test('enforces source consistency and router-safe bounded targets', () => {
|
|
const invalidValues = [
|
|
{ source: { ...intent().source, sourceRowCount: 5 } },
|
|
{
|
|
source: {
|
|
...intent().source,
|
|
sourceRowCount: MAX_CLUSTER_LEGACY_ENV_SOURCE_ROWS + 1,
|
|
},
|
|
},
|
|
{
|
|
source: {
|
|
...intent().source,
|
|
sourceRowCount: MAX_CLUSTER_LEGACY_ENV_EFFECTIVE_BINDINGS + 1,
|
|
activeRowCount: MAX_CLUSTER_LEGACY_ENV_EFFECTIVE_BINDINGS + 1,
|
|
disabledRowCount: 0,
|
|
effectiveBindingCount: MAX_CLUSTER_LEGACY_ENV_EFFECTIVE_BINDINGS + 1,
|
|
},
|
|
},
|
|
{
|
|
target: {
|
|
...intent().target,
|
|
taskCount: MAX_CLUSTER_LEGACY_ENV_TASKS + 1,
|
|
},
|
|
},
|
|
{
|
|
target: {
|
|
...intent().target,
|
|
triggerCount: MAX_CLUSTER_LEGACY_ENV_TRIGGERS + 1,
|
|
},
|
|
},
|
|
{
|
|
target: {
|
|
...intent().target,
|
|
totalEffectiveBytes: MAX_CLUSTER_LEGACY_ENV_EFFECTIVE_BYTES + 1,
|
|
},
|
|
},
|
|
];
|
|
for (const invalidValue of invalidValues) {
|
|
assert.throws(
|
|
() => normalizeClusterLegacyEnvMigrationPlanIntent(intent(invalidValue)),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('rejects widened shapes, accessors, symbols and tampered durable digests', () => {
|
|
const widened = intent();
|
|
widened.source.envName = 'TOKEN';
|
|
assert.throws(
|
|
() => normalizeClusterLegacyEnvMigrationPlanIntent(widened),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
|
|
const symbol = intent();
|
|
symbol.target[Symbol('secretValue')] = 'not-storable';
|
|
assert.throws(
|
|
() => normalizeClusterLegacyEnvMigrationPlanIntent(symbol),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
|
|
const accessor = intent();
|
|
Object.defineProperty(accessor.source, 'sourceRowCount', {
|
|
enumerable: true,
|
|
get() {
|
|
return 4;
|
|
},
|
|
});
|
|
assert.throws(
|
|
() => normalizeClusterLegacyEnvMigrationPlanIntent(accessor),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
|
|
const plan = createClusterLegacyEnvMigrationPlan(intent(), 12_345);
|
|
assert.throws(
|
|
() =>
|
|
normalizeClusterLegacyEnvMigrationPlan({
|
|
...plan,
|
|
planDigest: 'f'.repeat(64),
|
|
}),
|
|
InvalidClusterLegacyEnvMigrationPlanError,
|
|
);
|
|
});
|