97 lines
3.9 KiB
JavaScript
97 lines
3.9 KiB
JavaScript
/**
|
||
* 引擎工具回调的端到端用例(任务 A2.5)。
|
||
*
|
||
* 路径:HTTP 入口(/internal/tool-call)→ 应用服务 → Core.invoke → 假 Provider → 回执与执行记录。
|
||
* 同时验证鉴权:只允许 loopback + 内部令牌。
|
||
*/
|
||
import assert from 'node:assert/strict';
|
||
import { test } from 'node:test';
|
||
|
||
async function startWithProvider() {
|
||
process.env.APS_DEV_PROVIDER = '1';
|
||
const { startServer } = await import('../server/dist/index.js');
|
||
const server = await startServer({ host: '127.0.0.1', port: 0, version: 'test' });
|
||
const created = await fetch(`${server.url}/v1/sessions`, {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({ scope: 'project', projectId: 'p-default', title: '工具回调会话' }),
|
||
});
|
||
const session = (await created.json()).session;
|
||
return { server, session };
|
||
}
|
||
|
||
const callTool = (server, body, token) =>
|
||
fetch(`${server.url}/internal/tool-call`, {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json', ...(token === undefined ? {} : { 'x-aps-internal-token': token }) },
|
||
body: JSON.stringify(body),
|
||
});
|
||
|
||
test('HTTP → 应用服务 → Core → 假 Provider 的完整链路', async () => {
|
||
const { server, session } = await startWithProvider();
|
||
try {
|
||
const response = await callTool(
|
||
server,
|
||
{ sessionId: session.sessionId, capabilityId: 'fake.echo', params: { text: '端到端' } },
|
||
server.internalToken,
|
||
);
|
||
assert.equal(response.status, 200);
|
||
const receipt = await response.json();
|
||
assert.equal(receipt.status, 'succeeded');
|
||
assert.deepEqual(receipt.result, { echo: '端到端' });
|
||
|
||
// 执行记录:身份取自会话归属者,参数只留摘要
|
||
const record = server.service.core.executions.get(receipt.executionId);
|
||
assert.ok(record);
|
||
assert.equal(record.sessionId, session.sessionId);
|
||
assert.equal(record.projectId, 'p-default');
|
||
assert.equal(record.subjectUserId, 'dev-user', '身份应取自会话归属者,而不是调用方自带');
|
||
assert.match(record.paramsDigest, /^sha256:/);
|
||
|
||
const lookup = await fetch(`${server.url}/v1/executions/${receipt.executionId}`);
|
||
assert.equal(lookup.status, 200);
|
||
assert.equal((await lookup.json()).status, 'succeeded');
|
||
} finally {
|
||
delete process.env.APS_DEV_PROVIDER;
|
||
await server.close();
|
||
}
|
||
});
|
||
|
||
test('内部令牌缺失或错误时拒绝,且不产生执行记录', async () => {
|
||
const { server, session } = await startWithProvider();
|
||
try {
|
||
const body = { sessionId: session.sessionId, capabilityId: 'fake.echo', params: { text: 'x' } };
|
||
const missing = await callTool(server, body);
|
||
assert.equal(missing.status, 401);
|
||
assert.equal((await missing.json()).error.code, 'unauthorized');
|
||
|
||
const wrong = await callTool(server, body, 'not-the-token');
|
||
assert.equal(wrong.status, 401);
|
||
|
||
const before = server.service.core.executions.list().length;
|
||
const bad = await callTool(server, { sessionId: session.sessionId }, server.internalToken);
|
||
assert.equal(bad.status, 400, '缺 capabilityId 应 400');
|
||
assert.equal(server.service.core.executions.list().length, before, '被拒的调用不应产生执行记录');
|
||
} finally {
|
||
delete process.env.APS_DEV_PROVIDER;
|
||
await server.close();
|
||
}
|
||
});
|
||
|
||
test('未知能力经回调返回失败回执(不是 HTTP 错误)', async () => {
|
||
const { server, session } = await startWithProvider();
|
||
try {
|
||
const response = await callTool(
|
||
server,
|
||
{ sessionId: session.sessionId, capabilityId: 'fake.__nope__', params: {} },
|
||
server.internalToken,
|
||
);
|
||
assert.equal(response.status, 200, '能力不存在属于业务失败,不是协议错误');
|
||
const receipt = await response.json();
|
||
assert.equal(receipt.status, 'failed');
|
||
assert.equal(receipt.errorKind, 'invalid_argument');
|
||
} finally {
|
||
delete process.env.APS_DEV_PROVIDER;
|
||
await server.close();
|
||
}
|
||
}); |