Files
qinglong/packages/ql3-cluster-admin/test/automationManagementTransport.test.cjs
T

235 lines
6.6 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createTaskDefinitionRecord,
} = require('@qinglong/runtime-core/task-definition');
const { createTriggerRecord } = require('@qinglong/runtime-core/trigger');
const {
ClusterAutomationManagementTransportAuthenticationError,
ClusterAutomationManagementTransportRequestError,
createClusterAutomationManagementTransport,
} = require('@qinglong/cluster-admin/automation-management-transport');
const principal = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'operator-a' }),
authenticationId: 'session-operator-a',
authenticatedAtMs: 900,
expiresAtMs: 10_000,
assurance: 'hardware',
});
const taskCommand = Object.freeze({
projectId: 'project-a',
taskId: 'task-a',
expectedRevision: null,
mutationId: '123e4567-e89b-42d3-a456-426614174000',
name: 'Sensitive task name',
kind: 'script',
spec: {
schema: 'qinglong/script@v1',
config: { source: 'sensitive script body' },
},
labels: {},
enabled: true,
occurredAtMs: 1_000,
});
const triggerCommand = Object.freeze({
projectId: 'project-a',
triggerId: 'trigger-a',
expectedRevision: null,
mutationId: '123e4567-e89b-42d3-a456-426614174002',
taskId: 'task-a',
taskRevision: 1,
taskContentDigest: 'a'.repeat(64),
spec: {
schema: 'qinglong/cron@v1',
config: {
expression: '*/5 * * * *',
timezone: 'UTC',
misfirePolicy: 'fire_once',
},
},
enabled: true,
occurredAtMs: 1_000,
});
test('routes strong User Task/Trigger publications and returns only low-sensitive summaries', async () => {
const calls = [];
const transport = createClusterAutomationManagementTransport({
service: {
async publishTask(request) {
calls.push(['task', request]);
return {
status: 'created',
definition: createTaskDefinitionRecord(request.command, 900),
};
},
async publishTrigger(request) {
calls.push(['trigger', request]);
return {
status: 'created',
trigger: createTriggerRecord(request.command, 900),
};
},
async inspectTask(request) {
calls.push(['task-inspect', request]);
return createTaskDefinitionRecord(taskCommand, 900);
},
async listTasks(request) {
calls.push(['task-list', request]);
return {
definitions: [createTaskDefinitionRecord(taskCommand, 900)],
truncated: true,
next: { taskId: 'task-a' },
};
},
async inspectTrigger(request) {
calls.push(['trigger-inspect', request]);
return null;
},
async listTriggers(request) {
calls.push(['trigger-list', request]);
return {
triggers: [createTriggerRecord(triggerCommand, 900)],
truncated: false,
};
},
},
now: () => 1_100,
});
const authentication = { async authenticate() { return principal; } };
const task = await transport.execute(
{
schemaVersion: 1,
operation: 'task.publish',
request: { requestId: 'request-task', command: taskCommand },
},
authentication,
);
const trigger = await transport.execute(
{
schemaVersion: 1,
operation: 'trigger.publish',
request: { requestId: 'request-trigger', command: triggerCommand },
},
authentication,
);
const taskInspection = await transport.execute(
{
schemaVersion: 1,
operation: 'task.inspect',
request: {
requestId: 'request-task-inspect',
auditEventId: '123e4567-e89b-42d3-a456-426614174010',
projectId: 'project-a',
taskId: 'task-a',
},
},
authentication,
);
const taskList = await transport.execute(
{
schemaVersion: 1,
operation: 'task.list',
request: {
requestId: 'request-task-list',
auditEventId: '123e4567-e89b-42d3-a456-426614174011',
projectId: 'project-a',
limit: 1,
},
},
authentication,
);
const triggerInspection = await transport.execute(
{
schemaVersion: 1,
operation: 'trigger.inspect',
request: {
requestId: 'request-trigger-inspect',
auditEventId: '123e4567-e89b-42d3-a456-426614174012',
projectId: 'project-a',
triggerId: 'trigger-a',
},
},
authentication,
);
const triggerList = await transport.execute(
{
schemaVersion: 1,
operation: 'trigger.list',
request: {
requestId: 'request-trigger-list',
auditEventId: '123e4567-e89b-42d3-a456-426614174013',
projectId: 'project-a',
limit: 1,
},
},
authentication,
);
assert.deepEqual(calls.map(([kind]) => kind), [
'task',
'trigger',
'task-inspect',
'task-list',
'trigger-inspect',
'trigger-list',
]);
assert.deepEqual(calls.map(([, request]) => request.principal), [
principal,
principal,
principal,
principal,
principal,
principal,
]);
assert.equal(task.task.contentDigest.length, 64);
assert.equal(trigger.trigger.taskContentDigest, 'a'.repeat(64));
assert.equal(taskInspection.status, 'found');
assert.equal(taskList.next.taskId, 'task-a');
assert.equal(triggerInspection.status, 'absent');
assert.equal(triggerList.next, null);
const serialized = JSON.stringify([
task,
trigger,
taskInspection,
taskList,
triggerInspection,
triggerList,
]);
assert.doesNotMatch(serialized, /Sensitive|script body|expression|authenticationId|mutationId/);
});
test('rejects malformed envelopes and weak identities before service authority', async () => {
let calls = 0;
const transport = createClusterAutomationManagementTransport({
service: {
async publishTask() { calls += 1; },
async publishTrigger() { calls += 1; },
async inspectTask() { calls += 1; },
async listTasks() { calls += 1; },
async inspectTrigger() { calls += 1; },
async listTriggers() { calls += 1; },
},
now: () => 1_100,
});
await assert.rejects(
transport.execute(
{ schemaVersion: 1, operation: 'task.publish', request: {}, extra: true },
{ async authenticate() { return principal; } },
),
ClusterAutomationManagementTransportRequestError,
);
await assert.rejects(
transport.execute(
{
schemaVersion: 1,
operation: 'task.publish',
request: { requestId: 'request-task', command: taskCommand },
},
{ async authenticate() { return { ...principal, assurance: 'single_factor' }; } },
),
ClusterAutomationManagementTransportAuthenticationError,
);
assert.equal(calls, 0);
});