666db0b939
Persist goal runs in MySQL, bind agent runs to checkpoints, expose awaiting-approval UX in chat, and add admin inspection routes with local verify scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
227 lines
6.1 KiB
JavaScript
227 lines
6.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { attachPortalGoalRunRoutes } from './portal-goal-run-routes.mjs';
|
|
|
|
function createRouterRecorder() {
|
|
const routes = new Map();
|
|
return {
|
|
routes,
|
|
get(path, handler) {
|
|
routes.set(`GET ${path}`, handler);
|
|
},
|
|
post(path, handler) {
|
|
routes.set(`POST ${path}`, handler);
|
|
},
|
|
delete(path, handler) {
|
|
routes.set(`DELETE ${path}`, handler);
|
|
},
|
|
};
|
|
}
|
|
|
|
function createResponseRecorder() {
|
|
return {
|
|
statusCode: 200,
|
|
body: undefined,
|
|
status(code) {
|
|
this.statusCode = code;
|
|
return this;
|
|
},
|
|
json(body) {
|
|
this.body = body;
|
|
return this;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createRequest(overrides = {}) {
|
|
return {
|
|
body: {},
|
|
query: {},
|
|
params: {},
|
|
currentUser: { id: 'user-canary' },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function enabledGoalRunService(overrides = {}) {
|
|
return {
|
|
async createGoalRun(input) {
|
|
return {
|
|
id: 'goal-1',
|
|
title: input.title,
|
|
intentSummary: input.intentSummary,
|
|
checkpoints: [{ id: 'cp-1', title: '启动', status: 'pending' }],
|
|
currentCheckpointId: 'cp-1',
|
|
};
|
|
},
|
|
async listGoalRuns() {
|
|
return [{ id: 'goal-1', title: '长期任务', status: 'active' }];
|
|
},
|
|
async getGoalRun({ goalRunId }) {
|
|
if (goalRunId !== 'goal-1') return null;
|
|
return {
|
|
id: 'goal-1',
|
|
title: '长期任务',
|
|
checkpoints: [{ id: 'cp-1', title: '启动', status: 'running' }],
|
|
};
|
|
},
|
|
async approveCheckpoint() {
|
|
return { id: 'goal-1', checkpoints: [{ id: 'cp-1', status: 'approved' }] };
|
|
},
|
|
async pauseGoal() {
|
|
return { id: 'goal-1', status: 'paused' };
|
|
},
|
|
async resumeGoal() {
|
|
return { goal: { id: 'goal-1', status: 'active' }, checkpointId: 'cp-2' };
|
|
},
|
|
async cancelGoal() {
|
|
return { id: 'goal-1', status: 'cancelled' };
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const enabledEnv = {
|
|
GOAL_RUN_ENABLED: '1',
|
|
GOAL_RUN_CANARY_USER_IDS: 'user-canary',
|
|
};
|
|
|
|
test('Goal Run routes preserve MVP inventory', () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalGoalRunRoutes(api, {
|
|
getGoalRunService: () => enabledGoalRunService(),
|
|
env: enabledEnv,
|
|
});
|
|
assert.deepEqual([...api.routes.keys()], [
|
|
'POST /goals',
|
|
'GET /goals',
|
|
'GET /goals/:goalRunId',
|
|
'POST /goals/:goalRunId/checkpoints/:checkpointId/approve',
|
|
'POST /goals/:goalRunId/pause',
|
|
'POST /goals/:goalRunId/resume',
|
|
'DELETE /goals/:goalRunId',
|
|
]);
|
|
});
|
|
|
|
test('POST /goals returns 503 when service unavailable', async () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalGoalRunRoutes(api, {
|
|
getGoalRunService: () => null,
|
|
env: enabledEnv,
|
|
});
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /goals')(
|
|
createRequest({
|
|
body: { title: '任务', intentSummary: '分阶段完成' },
|
|
}),
|
|
res,
|
|
);
|
|
assert.equal(res.statusCode, 503);
|
|
});
|
|
|
|
test('POST /goals creates goal for enabled canary user', async () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalGoalRunRoutes(api, {
|
|
getGoalRunService: () => enabledGoalRunService(),
|
|
env: enabledEnv,
|
|
});
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /goals')(
|
|
createRequest({
|
|
body: {
|
|
title: '准备下季度产品规划',
|
|
intentSummary: '收集竞品并输出草案',
|
|
},
|
|
}),
|
|
res,
|
|
);
|
|
assert.equal(res.statusCode, 201);
|
|
assert.equal(res.body.goal.id, 'goal-1');
|
|
});
|
|
|
|
test('GET /goals rejects non-canary user', async () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalGoalRunRoutes(api, {
|
|
getGoalRunService: () => enabledGoalRunService(),
|
|
env: enabledEnv,
|
|
});
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('GET /goals')(
|
|
createRequest({ currentUser: { id: 'other-user' } }),
|
|
res,
|
|
);
|
|
assert.equal(res.statusCode, 403);
|
|
});
|
|
|
|
test('POST /goals/:id/resume returns checkpoint binding hint', async () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalGoalRunRoutes(api, {
|
|
getGoalRunService: () => enabledGoalRunService(),
|
|
env: enabledEnv,
|
|
});
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /goals/:goalRunId/resume')(
|
|
createRequest({ params: { goalRunId: 'goal-1' } }),
|
|
res,
|
|
);
|
|
assert.equal(res.statusCode, 200);
|
|
assert.equal(res.body.checkpointId, 'cp-2');
|
|
});
|
|
|
|
test('POST approve auto-dispatches agent run when session_id provided', async () => {
|
|
const createdRuns = [];
|
|
const api = createRouterRecorder();
|
|
attachPortalGoalRunRoutes(api, {
|
|
getGoalRunService: () => enabledGoalRunService({
|
|
async getGoalRun({ goalRunId }) {
|
|
if (goalRunId !== 'goal-1') return null;
|
|
return {
|
|
id: 'goal-1',
|
|
title: '长期任务',
|
|
checkpoints: [
|
|
{ id: 'cp-1', title: '启动', status: 'approved' },
|
|
{ id: 'cp-2', title: '输出', status: 'pending' },
|
|
],
|
|
};
|
|
},
|
|
async startNextCheckpoint({ goalRunId }) {
|
|
return { goalRunId, checkpointId: 'cp-2' };
|
|
},
|
|
async approveCheckpoint() {
|
|
return {
|
|
id: 'goal-1',
|
|
status: 'active',
|
|
checkpoints: [{ id: 'cp-1', status: 'approved' }],
|
|
};
|
|
},
|
|
}),
|
|
getAgentRunGateway: () => ({
|
|
async createRun(userId, payload) {
|
|
createdRuns.push({ userId, payload });
|
|
return {
|
|
id: 'run-1',
|
|
userId,
|
|
sessionId: payload.sessionId,
|
|
requestId: payload.requestId,
|
|
status: 'queued',
|
|
};
|
|
},
|
|
}),
|
|
env: enabledEnv,
|
|
});
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /goals/:goalRunId/checkpoints/:checkpointId/approve')(
|
|
createRequest({
|
|
params: { goalRunId: 'goal-1', checkpointId: 'cp-1' },
|
|
body: { session_id: 'session-1', feedback: '继续' },
|
|
}),
|
|
res,
|
|
);
|
|
assert.equal(res.statusCode, 200);
|
|
assert.equal(res.body.goal.id, 'goal-1');
|
|
assert.equal(res.body.run.id, 'run-1');
|
|
assert.equal(createdRuns.length, 1);
|
|
assert.equal(createdRuns[0].payload.sessionId, 'session-1');
|
|
assert.equal(createdRuns[0].payload.goalRunId, 'goal-1');
|
|
});
|