Files
qinglong/packages/ql3-cluster-control/test/runAttemptLogReadRoute.test.cjs
T

322 lines
8.6 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createClusterControlAdmissionPipeline,
} = require('@qinglong/cluster-control/admission');
const {
createClusterControlRouteRegistry,
} = require('@qinglong/cluster-control/routes');
const {
CLUSTER_CONTROL_RUN_ATTEMPT_LOG_READ_ROUTE,
createClusterControlRunAttemptLogReadRoute,
} = require('../dist/run/runAttemptLogReadRoute.js');
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'usr_viewer' }),
authenticationId: 'session:viewer',
authenticatedAtMs: 9_000,
expiresAtMs: 11_000,
assurance: 'single_factor',
});
function run(overrides = {}) {
return {
id: 'run_123',
projectId: 'prj_default',
taskId: 'task_1',
taskRevision: 'revision_1',
triggerType: 'task_start',
executionOrigin: 'manual',
executionOwner: 'runtime',
status: 'running',
version: 2,
eventSequence: 2,
priority: 0,
createdAtMs: 1,
...overrides,
};
}
function attempt(overrides = {}) {
return {
id: 'attempt_123',
runId: 'run_123',
attempt: 1,
status: 'running',
executorType: 'remote_worker',
logArtifactId: `wlog-${'a'.repeat(30)}`,
callbackSequence: 0,
createdAtMs: 1,
...overrides,
};
}
function metadata(query = {}) {
return Object.freeze({
requestId: 'request-log-read',
method: 'GET',
path: '/api/v3/projects/prj_default/runs/run_123/attempts/attempt_123/log',
query: Object.freeze(query),
headers: Object.freeze({ authorization: 'Bearer opaque' }),
signal: new AbortController().signal,
});
}
function pipeline(options = {}) {
const events = options.events ?? [];
const repository = options.repository ?? {
async findRunById() {
events.push('run');
return run();
},
async findAttemptById() {
events.push('attempt');
return attempt();
},
};
const reader = options.reader ?? {
async read(identity, range) {
events.push(`storage:${range.offset}:${range.length}`);
return {
status: 'available',
content: Buffer.from('cluster-log'),
start: range.offset,
endExclusive: range.offset + 11,
totalBytes: range.offset + 20,
nextOffset: range.offset + 11,
truncation: { truncated: false },
};
},
};
return createClusterControlAdmissionPipeline({
routes: createClusterControlRouteRegistry([
createClusterControlRunAttemptLogReadRoute(repository, reader),
]),
authenticator: {
authenticate() {
events.push('authenticate');
return PRINCIPAL;
},
},
policy: {
authorize(request) {
events.push(`authorize:${request.permission}`);
return options.effect
? { effect: options.effect, reasons: ['masked'], fence: null }
: {
effect: 'allow',
reasons: ['role_grant'],
fence: { projectVersion: 2, bindingVersion: 3 },
};
},
},
audit: {
record(record) {
events.push(`audit:${record.outcome}`);
},
},
now: () => 10_000,
});
}
async function invoke(value, query = {}) {
const prepared = await value.prepare(metadata(query));
return prepared.handle(null);
}
test('publishes the immutable Artifact-scoped route contract', () => {
assert.deepEqual(CLUSTER_CONTROL_RUN_ATTEMPT_LOG_READ_ROUTE, {
method: 'GET',
path: '/api/v3/projects/{projectId}/runs/{runId}/attempts/{attemptId}/log',
operationId: 'run.log.read',
permission: 'artifact.read',
projectParameter: 'projectId',
allowedQuery: ['length', 'offset'],
});
});
test('authorizes and audits before metadata and one bounded range read', async () => {
const events = [];
const result = await invoke(pipeline({ events }), {
offset: ['4'],
length: ['16'],
});
assert.equal(result.statusCode, 200);
assert.equal(result.body.schema, 'qinglong/run-attempt-log-read-result@v1');
assert.equal(
Buffer.from(result.body.content, 'base64').toString(),
'cluster-log',
);
assert.deepEqual(result.body.range, {
start: 4,
endExclusive: 15,
totalBytes: 24,
nextOffset: 15,
});
assert.deepEqual(events, [
'authenticate',
'authorize:artifact.read',
'audit:allowed',
'run',
'attempt',
'storage:4:16',
]);
});
test('uses the Cluster default window and rejects unbounded query values', async () => {
const events = [];
assert.equal((await invoke(pipeline({ events }))).statusCode, 200);
assert.equal(events.at(-1), `storage:0:${64 * 1024}`);
for (const query of [
{ offset: ['-1'] },
{ offset: ['04'] },
{ length: ['0'] },
{ length: [String(256 * 1024 + 1)] },
{ length: ['1', '2'] },
]) {
await assert.rejects(
pipeline().prepare(metadata(query)),
(error) =>
error.statusCode === 400 && error.code === 'invalid_route_query',
);
}
});
test('masks deny and approval without reading Run or object storage', async () => {
for (const effect of ['deny', 'require_approval']) {
let touched = false;
await assert.rejects(
pipeline({
effect,
repository: {
async findRunById() {
touched = true;
return run();
},
async findAttemptById() {
touched = true;
return attempt();
},
},
reader: {
async read() {
touched = true;
return { status: 'missing' };
},
},
}).prepare(metadata()),
(error) =>
error.statusCode === 404 && error.code === 'artifact_not_found',
);
assert.equal(touched, false);
}
});
test('returns pending during upload and fails closed without an object reader', async () => {
const pending = pipeline({
reader: {
async read() {
return { status: 'missing' };
},
},
});
assert.equal((await invoke(pending)).statusCode, 202);
const unavailableRoute = createClusterControlRunAttemptLogReadRoute({
async findRunById() {
return run();
},
async findAttemptById() {
return attempt();
},
});
const prepared = await createClusterControlAdmissionPipeline({
routes: createClusterControlRouteRegistry([unavailableRoute]),
authenticator: { authenticate: () => PRINCIPAL },
policy: {
authorize: () => ({
effect: 'allow',
reasons: ['role_grant'],
fence: { projectVersion: 1, bindingVersion: 1 },
}),
},
audit: { record() {} },
now: () => 10_000,
}).prepare(metadata());
assert.deepEqual(await prepared.handle(null), {
statusCode: 503,
body: { code: 'artifact_unavailable' },
});
});
test('maps an injected durable retention state to 410 without object access', async () => {
const {
createRunAttemptLogRetirementRecord,
} = require('@qinglong/runtime-core/run-attempt-log-retention');
let reads = 0;
const route = createClusterControlRunAttemptLogReadRoute(
{
async findRunById() {
return run({ status: 'succeeded', finishedAtMs: 10 });
},
async findAttemptById() {
return attempt({ status: 'succeeded', finishedAtMs: 10 });
},
},
{
async read() {
reads += 1;
return { status: 'missing' };
},
},
{
async inspect() {
return {
status: 'retired',
record: createRunAttemptLogRetirementRecord({
projectId: 'prj_default',
runId: 'run_123',
attemptId: 'attempt_123',
logArtifactId: `wlog-${'a'.repeat(30)}`,
executorType: 'remote_worker',
finishedAtMs: 10,
eligibleAtMs: 20,
retiredAtMs: 30,
disposition: 'deleted',
byteLength: 42,
truncation: { truncated: 'unknown' },
}),
};
},
},
);
const prepared = await createClusterControlAdmissionPipeline({
routes: createClusterControlRouteRegistry([route]),
authenticator: { authenticate: () => PRINCIPAL },
policy: {
authorize: () => ({
effect: 'allow',
reasons: ['role_grant'],
fence: { projectVersion: 1, bindingVersion: 1 },
}),
},
audit: { record() {} },
now: () => 10_000,
}).prepare(metadata());
assert.deepEqual(await prepared.handle(null), {
statusCode: 410,
body: {
schema: 'qinglong/run-attempt-log-read-result@v1',
status: 'retired',
projectId: 'prj_default',
runId: 'run_123',
attemptId: 'attempt_123',
retiredAtMs: 30,
byteLength: 42,
truncation: { truncated: 'unknown' },
},
});
assert.equal(reads, 0);
});