Files
cc-web/scripts/gitea-workflow-codex-unit.js

154 lines
6.9 KiB
JavaScript

'use strict';
/** Gitea Workflow Codex/App 协议适配器离线单测。 */
const assert = require('node:assert/strict');
const {
buildGiteaMcpServerConfig,
buildGiteaThreadConfig,
buildGiteaMcpConfig,
extractTurnId,
extractThreadId,
buildWorkflowMarker,
parseWorkflowMarker,
encodeMarker,
isBotComment,
createGiteaWorkflowCodex,
WORKFLOW_STATES,
} = require('../lib/gitea-workflow-codex');
async function main() {
const mcp = buildGiteaMcpServerConfig({
host: 'https://gitea.example/',
accessToken: 'secret-token',
});
assert.deepEqual(mcp.args.slice(0, 4), ['-t', 'stdio', '-H', 'https://gitea.example']);
assert.equal(mcp.env.GITEA_ACCESS_TOKEN, 'secret-token');
assert.equal(mcp.env.GITEA_HOST, 'https://gitea.example');
assert.equal(buildGiteaMcpConfig({ gitea: { host: 'https://gitea.example', token: 't' } }).type, 'stdio');
const thread = buildGiteaThreadConfig({
cwd: '/tmp/gitea-workspace',
host: 'https://gitea.example',
accessToken: 'secret-token',
});
assert.equal(thread.config['mcp_servers.gitea'].type, 'stdio');
assert.equal(thread.config['mcp_servers.gitea'].env.GITEA_ACCESS_TOKEN, 'secret-token');
assert.equal(Object.hasOwn(thread.config, 'model'), false);
assert.equal(extractTurnId({ params: { turn: { id: 'turn-1' } } }), 'turn-1');
assert.equal(extractThreadId({ params: { thread: { id: 'thread-1' } } }), 'thread-1');
const marker = buildWorkflowMarker({ taskId: 'task-1', turnId: 'turn-1', resourceKey: 'default:a/r:issue:1' });
assert.deepEqual(parseWorkflowMarker(`正文\n${marker}`), {
version: '1', taskId: 'task-1', turnId: 'turn-1', resourceKey: 'default:a/r:issue:1', kind: 'final',
});
assert.equal(parseWorkflowMarker('<!-- ccweb-gitea v="1" taskId="x" -->'), null);
assert.equal(encodeMarker({ taskId: 'task-1', turnId: 'turn-1', resourceKey: 'r', state: 'fallback' }).includes('kind="fallback"'), true);
const bot = { id: '42', login: 'ccweb-bot' };
assert.equal(isBotComment({ user: { id: 42, login: 'renamed-bot' } }, bot), true);
assert.equal(isBotComment({ user: { login: 'CCWEB-BOT' } }, bot), true);
assert.equal(isBotComment({ user: { login: 'alice' } }, bot), false);
assert.equal(
createGiteaWorkflowCodex({ botIdentity: bot }).matchWorkflowReply(
{ body: `已完成\n${buildWorkflowMarker({ taskId: 'task-1', turnId: 'turn-old', resourceKey: 'r' })}`, user: bot },
{ taskId: 'task-1', turnId: 'turn-new', turnIds: ['turn-new', 'turn-old'], resourceKey: 'r' },
),
true,
);
let nowTick = 0;
const retryCalls = [];
const restCalls = [];
let verifyCalls = 0;
const adapter = createGiteaWorkflowCodex({
botIdentity: bot,
now: () => new Date(Date.UTC(2026, 7, 24, 0, 0, nowTick++)),
verifyReply: async ({ source, expected }) => {
verifyCalls += 1;
if (source === 'mcp' && verifyCalls === 1) return { status: 'missing' };
if (source === 'mcp' && verifyCalls === 2) return {
comments: [{
id: 99,
body: `已完成\n${buildWorkflowMarker(expected)}`,
user: { id: 42, login: 'ccweb-bot' },
}],
};
return { status: 'missing' };
},
startTurn: async ({ threadId, kind, hidden, prompt }) => {
retryCalls.push({ threadId, kind, hidden, prompt });
return { turnId: 'turn-retry-1' };
},
sendRestReply: async (input) => {
restCalls.push(input);
return { confirmed: true, commentId: 100 };
},
});
adapter.createTask({ taskId: 'task-1', sessionKey: 'default:r:issue:1', resourceKey: 'default:r:issue:1' });
adapter.attachThread('task-1', 'thread-1');
adapter.handleTurnStarted({ taskId: 'task-1', threadId: 'thread-1', turnId: 'turn-1' });
const retry = await adapter.handleTurnCompleted({ taskId: 'task-1', turnId: 'turn-1', finalText: '最终答案' });
assert.equal(retry.event, 'reply_retry_started');
assert.equal(retryCalls.length, 1);
assert.equal(retryCalls[0].threadId, 'thread-1');
assert.equal(retryCalls[0].kind, 'reply_retry');
assert.equal(retryCalls[0].hidden, true);
const confirmed = await adapter.handleTurnCompleted({ taskId: 'task-1', turnId: 'turn-retry-1', finalText: '最终答案' });
assert.equal(confirmed.event, 'reply_confirmed');
assert.equal(confirmed.source, 'mcp');
assert.equal(restCalls.length, 0);
let unknownRetry = 0;
let unknownRest = 0;
const unknown = createGiteaWorkflowCodex({
verifyReply: async () => ({ status: 'unknown' }),
startTurn: async () => { unknownRetry += 1; return { turnId: 'should-not-run' }; },
sendRestReply: async () => { unknownRest += 1; return { confirmed: true }; },
});
unknown.createTask({ taskId: 'task-unknown', sessionKey: 's', resourceKey: 'r' });
unknown.handleTurnStarted({ taskId: 'task-unknown', threadId: 'thread-u', turnId: 'turn-u' });
const unknownResult = await unknown.handleTurnCompleted({ taskId: 'task-unknown', turnId: 'turn-u', finalText: 'x' });
assert.equal(unknownResult.event, 'reply_verification_unknown');
assert.equal(unknownRetry, 0);
assert.equal(unknownRest, 0);
assert.equal(unknown.getTask('task-unknown').state, WORKFLOW_STATES.FAILED_REPLY);
const continuation = adapter.queueWaitingUserComment({
taskId: 'task-1',
nextTaskId: 'task-2',
comment: { id: 7, body: '@ccweb-bot 请继续', user: { id: 9, login: 'alice' } },
});
assert.equal(continuation.threadId, 'thread-1');
assert.equal(adapter.getTask('task-2').parentTaskId, 'task-1');
const waitingAdapter = createGiteaWorkflowCodex({ botIdentity: bot });
waitingAdapter.createTask({ taskId: 'task-wait', sessionKey: 's-wait', resourceKey: 'r-wait', threadId: 'thread-wait' });
waitingAdapter.handleTurnStarted({ taskId: 'task-wait', threadId: 'thread-wait', turnId: 'turn-wait' });
const waiting = waitingAdapter.handleTurnEvent({
taskId: 'task-wait',
notification: { method: 'item/tool/requestUserInput', params: { turnId: 'turn-wait', questions: [{ id: 'q1' }] } },
});
assert.equal(waiting.event, 'waiting_user');
assert.equal(waitingAdapter.getTask('task-wait').state, WORKFLOW_STATES.WAITING_USER);
const waitingContinuation = waitingAdapter.queueWaitingUserComment({
taskId: 'task-wait', nextTaskId: 'task-wait-2',
comment: { id: 10, body: '@ccweb-bot 答案是 main', user: { id: 9, login: 'alice' } },
});
assert.equal(waitingContinuation.threadId, 'thread-wait');
assert.equal(adapter.queueWaitingUserComment({
taskId: 'task-1', nextTaskId: 'task-bot',
comment: { id: 8, body: 'bot', user: { id: 42, login: 'ccweb-bot' } },
}).ignored, true);
assert.equal(adapter.queueWaitingUserComment({
taskId: 'task-1', nextTaskId: 'task-marker',
comment: { id: 9, body: `重复回执 ${buildWorkflowMarker({ taskId: 'task-1', turnId: 'turn-1', resourceKey: 'default:r:issue:1' })}`, user: { id: 9, login: 'alice' } },
}).ignored, true);
console.log('Gitea Workflow Codex unit checks passed.');
}
main().catch((error) => {
console.error(error.stack || error.message || error);
process.exitCode = 1;
});