mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:21:50 +08:00
299 lines
8.2 KiB
JavaScript
299 lines
8.2 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
const { createLocalSecretRef } = require('../dist/secret/localSecret');
|
|
const { createSecretRef } = require('../dist/secret/secretReference');
|
|
const {
|
|
BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
InvalidTaskSpecSemanticError,
|
|
TaskSpecSemanticRegistry,
|
|
UnsupportedTaskSpecError,
|
|
createBuiltInTaskSpecSemanticRegistry,
|
|
createTaskSpecSemanticRegistry,
|
|
} = require('../dist/task-definition/taskSpecSemantic');
|
|
|
|
function context(overrides = {}) {
|
|
return {
|
|
projectId: 'default',
|
|
taskId: 'task-1',
|
|
kind: 'command',
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: ['hello'] },
|
|
},
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('publishes one immutable built-in schema and canonicalizes command specs', () => {
|
|
const registry = createBuiltInTaskSpecSemanticRegistry();
|
|
assert.equal(Object.isFrozen(registry), true);
|
|
assert.equal('register' in registry, false);
|
|
assert.deepEqual(registry.list(), [
|
|
{ schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA, kind: 'command' },
|
|
]);
|
|
assert.equal(
|
|
registry.supports('command', BUILT_IN_COMMAND_TASK_SPEC_SCHEMA),
|
|
true,
|
|
);
|
|
|
|
const secretRef = createLocalSecretRef({
|
|
projectId: 'default',
|
|
name: 'TOKEN',
|
|
});
|
|
const normalized = registry.normalize(
|
|
context({
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
timeoutMs: 5_000,
|
|
workingDirectory: '/work',
|
|
environment: [
|
|
{ kind: 'secret', name: 'TOKEN', secretRef },
|
|
{ kind: 'public', name: 'EMPTY', value: '' },
|
|
],
|
|
command: { kind: 'shell', command: 'echo "$EMPTY"' },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.deepEqual(JSON.parse(JSON.stringify(normalized)), {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: {
|
|
kind: 'shell',
|
|
command: 'echo "$EMPTY"',
|
|
shell: '/bin/sh',
|
|
},
|
|
environment: [
|
|
{ name: 'EMPTY', kind: 'public', value: '' },
|
|
{ name: 'TOKEN', kind: 'secret', secretRef },
|
|
],
|
|
timeoutMs: 5_000,
|
|
workingDirectory: '/work',
|
|
},
|
|
});
|
|
assert.equal(Object.isFrozen(normalized.config.environment), true);
|
|
});
|
|
|
|
test('fails closed for unknown schemas, kind drift and unsafe command shapes', () => {
|
|
const registry = createBuiltInTaskSpecSemanticRegistry();
|
|
assert.throws(
|
|
() =>
|
|
registry.normalize(
|
|
context({
|
|
kind: 'script',
|
|
spec: { schema: 'qinglong/script@v1', config: {} },
|
|
}),
|
|
),
|
|
UnsupportedTaskSpecError,
|
|
);
|
|
assert.throws(
|
|
() => registry.normalize(context({ kind: 'script' })),
|
|
/schema does not match TaskDefinition kind/,
|
|
);
|
|
|
|
const invalidConfigs = [
|
|
{ command: { kind: 'argv', file: 'bin/echo', args: [] } },
|
|
{ command: { kind: 'shell', command: 'true', shell: '/usr/bin/zsh' } },
|
|
{
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
workingDirectory: 'work',
|
|
},
|
|
{
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
environment: [{ kind: 'public', name: 'QL3_TOKEN', value: 'x' }],
|
|
},
|
|
{
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
environment: [
|
|
{ kind: 'public', name: 'TOKEN', value: 'a' },
|
|
{ kind: 'public', name: 'TOKEN', value: 'b' },
|
|
],
|
|
},
|
|
{
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
environment: [
|
|
{
|
|
kind: 'secret',
|
|
name: 'TOKEN',
|
|
secretRef: createLocalSecretRef({
|
|
projectId: 'other',
|
|
name: 'TOKEN',
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
{ command: { kind: 'argv', file: '/bin/echo', args: [] }, extra: true },
|
|
];
|
|
for (const config of invalidConfigs) {
|
|
assert.throws(
|
|
() =>
|
|
registry.normalize(
|
|
context({
|
|
spec: { schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA, config },
|
|
}),
|
|
),
|
|
InvalidTaskSpecSemanticError,
|
|
);
|
|
}
|
|
assert.doesNotThrow(() =>
|
|
registry.normalize(
|
|
context({
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: [''] },
|
|
},
|
|
},
|
|
}),
|
|
),
|
|
);
|
|
});
|
|
|
|
test('accepts only an explicit bounded descriptor set and hides validator errors', () => {
|
|
const descriptor = {
|
|
schema: 'example/tool@v1',
|
|
kind: 'tool',
|
|
normalizeConfig() {
|
|
throw new Error('provider internals');
|
|
},
|
|
};
|
|
assert.throws(
|
|
() => new TaskSpecSemanticRegistry([descriptor, descriptor]),
|
|
/invalid or duplicated/,
|
|
);
|
|
const registry = new TaskSpecSemanticRegistry([descriptor]);
|
|
assert.throws(
|
|
() =>
|
|
registry.normalize(
|
|
context({
|
|
kind: 'tool',
|
|
spec: { schema: 'example/tool@v1', config: {} },
|
|
}),
|
|
),
|
|
(error) => {
|
|
assert.equal(error.message.includes('provider internals'), false);
|
|
return error instanceof InvalidTaskSpecSemanticError;
|
|
},
|
|
);
|
|
|
|
const extended = createTaskSpecSemanticRegistry([
|
|
{
|
|
schema: 'example/command@v1',
|
|
kind: 'command',
|
|
normalizeConfig: (config) => config,
|
|
},
|
|
]);
|
|
assert.deepEqual(
|
|
extended.list().map(({ schema }) => schema),
|
|
['example/command@v1', BUILT_IN_COMMAND_TASK_SPEC_SCHEMA],
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
createTaskSpecSemanticRegistry([
|
|
{
|
|
schema: 'qinglong/tool@v1',
|
|
kind: 'tool',
|
|
normalizeConfig: (config) => config,
|
|
},
|
|
]),
|
|
/reserved qinglong namespace/,
|
|
);
|
|
});
|
|
|
|
test('canonicalizes an optional bounded Remote Worker PlacementSpec in command semantics', () => {
|
|
const registry = createBuiltInTaskSpecSemanticRegistry();
|
|
const normalized = registry.normalize(
|
|
context({
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: ['placed'] },
|
|
placement: {
|
|
required: {
|
|
architectures: ['arm64'],
|
|
runtimes: [{ name: 'node', versionRange: '^24.0.0' }],
|
|
labels: { region: 'cn-east' },
|
|
},
|
|
preferred: [{ labels: { tier: 'edge' }, weight: 5 }],
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.deepEqual(JSON.parse(JSON.stringify(normalized.config.placement)), {
|
|
required: {
|
|
architectures: ['arm64'],
|
|
runtimes: [{ name: 'node', versionRange: '^24.0.0' }],
|
|
labels: { region: 'cn-east' },
|
|
},
|
|
preferred: [{ labels: { tier: 'edge' }, weight: 5 }],
|
|
});
|
|
assert.throws(
|
|
() =>
|
|
registry.normalize(
|
|
context({
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
placement: {
|
|
required: {
|
|
runtimes: [{ name: 'node', versionRange: 'not-semver' }],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
),
|
|
InvalidTaskSpecSemanticError,
|
|
);
|
|
});
|
|
|
|
test('accepts only a same-Project version-pinned environment bundle reference', () => {
|
|
const registry = createBuiltInTaskSpecSemanticRegistry();
|
|
const environmentBundleRef = createSecretRef({
|
|
projectId: 'default',
|
|
name: 'legacy-env-bundle',
|
|
version: 3,
|
|
});
|
|
const normalized = registry.normalize(
|
|
context({
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
environmentBundleRef,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(normalized.config.environmentBundleRef, environmentBundleRef);
|
|
for (const invalidRef of [
|
|
createSecretRef({ projectId: 'default', name: 'legacy-env-bundle' }),
|
|
createSecretRef({
|
|
projectId: 'other',
|
|
name: 'legacy-env-bundle',
|
|
version: 3,
|
|
}),
|
|
]) {
|
|
assert.throws(
|
|
() =>
|
|
registry.normalize(
|
|
context({
|
|
spec: {
|
|
schema: BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: [] },
|
|
environmentBundleRef: invalidRef,
|
|
},
|
|
},
|
|
}),
|
|
),
|
|
/environmentBundleRef/,
|
|
);
|
|
}
|
|
});
|