Files

172 lines
4.3 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
BUILTIN_RUN_EVENT_LIST_DEFAULT_LIMIT,
BUILTIN_RUN_EVENT_LIST_MAX_LIMIT,
BUILTIN_RUN_EVENT_LIST_TOOL,
BUILTIN_RUN_EVENT_LIST_TOOL_DEFINITION,
BuiltInRunEventListToolUnavailableError,
InvalidBuiltInRunEventListToolError,
executeBuiltInRunEventListTool,
} = require('../dist/tool-projection/runEventList.js');
function run(projectId = 'default') {
return Object.freeze({ id: 'run-1', projectId });
}
function event(sequence, overrides = {}) {
return Object.freeze({
id: `event-${sequence}`,
runId: 'run-1',
sequence,
type: `run.event.${sequence}`,
dedupeKey: `private-dedupe-${sequence}`,
actorType: 'system',
actorId: 'private-actor',
attemptId: 'private-attempt',
stepRunId: 'private-step',
payload: Object.freeze({ secret: 'must-not-leak' }),
createdAtMs: 1_000 + sequence,
...overrides,
});
}
test('defines one bounded low-risk Run event list Tool', () => {
assert.deepEqual(BUILTIN_RUN_EVENT_LIST_TOOL, {
name: 'qinglong.run.events.list',
version: '1.0.0',
});
assert.equal(BUILTIN_RUN_EVENT_LIST_TOOL_DEFINITION.effect, 'read');
assert.equal(BUILTIN_RUN_EVENT_LIST_TOOL_DEFINITION.risk, 'low');
assert.deepEqual(
BUILTIN_RUN_EVENT_LIST_TOOL_DEFINITION.requiredPermissions,
['run.read'],
);
assert.equal(BUILTIN_RUN_EVENT_LIST_DEFAULT_LIMIT, 32);
assert.equal(BUILTIN_RUN_EVENT_LIST_MAX_LIMIT, 64);
});
test('returns an ordered payload-free page and a stable cursor', async () => {
const calls = [];
const result = await executeBuiltInRunEventListTool(
{
async findRunById(runId) {
calls.push(['run', runId]);
return run();
},
async listEvents(runId, options) {
calls.push(['events', runId, options]);
return [event(3), event(4), event(5)];
},
},
'default',
{ runId: 'run-1', afterSequence: 2, limit: 2 },
);
assert.deepEqual(result, {
found: true,
events: [
{
sequence: 3,
type: 'run.event.3',
actorType: 'system',
createdAtMs: 1_003,
},
{
sequence: 4,
type: 'run.event.4',
actorType: 'system',
createdAtMs: 1_004,
},
],
hasMore: true,
nextAfterSequence: 4,
});
assert.deepEqual(calls, [
['run', 'run-1'],
['events', 'run-1', { afterSequence: 2, limit: 3 }],
]);
assert.equal(JSON.stringify(result).includes('must-not-leak'), false);
assert.equal(JSON.stringify(result).includes('private-'), false);
});
test('masks absent and cross-Project Runs without reading events', async () => {
for (const value of [null, run('other')]) {
let eventReads = 0;
const result = await executeBuiltInRunEventListTool(
{
async findRunById() {
return value;
},
async listEvents() {
eventReads += 1;
return [];
},
},
'default',
{ runId: 'run-1', afterSequence: 7 },
);
assert.deepEqual(result, {
found: false,
events: [],
hasMore: false,
nextAfterSequence: 7,
});
assert.equal(eventReads, 0);
}
});
test('rejects malformed input and fails closed on storage or event corruption', async () => {
const runs = {
async findRunById() {
return run();
},
async listEvents() {
return [];
},
};
for (const input of [
null,
{},
{ runId: '' },
{ runId: 'run-1', limit: 65 },
{ runId: 'run-1', afterSequence: -1 },
{ runId: 'run-1', unexpected: true },
]) {
await assert.rejects(
executeBuiltInRunEventListTool(runs, 'default', input),
InvalidBuiltInRunEventListToolError,
);
}
await assert.rejects(
executeBuiltInRunEventListTool(
{
async findRunById() {
throw new Error('hidden');
},
async listEvents() {
return [];
},
},
'default',
{ runId: 'run-1' },
),
BuiltInRunEventListToolUnavailableError,
);
await assert.rejects(
executeBuiltInRunEventListTool(
{
async findRunById() {
return run();
},
async listEvents() {
return [event(2), event(1)];
},
},
'default',
{ runId: 'run-1' },
),
BuiltInRunEventListToolUnavailableError,
);
});