chore: initial commit

This commit is contained in:
2026-10-22 17:07:08 +08:00
commit 0acef64f48
200 changed files with 35546 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
import assert from 'node:assert/strict';
import {EventEmitter, getEventListeners} from 'node:events';
import test from 'node:test';
import {register} from 'tsx/esm/api';
const loader = register({namespace: 'workflow-functions-test'});
const tsImport = loader.import;
test.after(() => loader.unregister());
const {create_function_manager, get_function_list} = await tsImport('../bot/function/init.ts', import.meta.url);
const {WorkflowControlFunction} = await tsImport('../bot/function/workflow/shared.ts', import.meta.url);
const names = ['run_once', 'stop', 'trigger_start', 'trigger_stop', 'stop_run', 'reload'];
const operations = Object.fromEntries(await Promise.all(names.map(async name => {
const {init} = await tsImport(`../bot/function/workflow/${name}.ts`, import.meta.url);
return [name, () => new init()];
})));
const turn = () => new Promise(resolve => setImmediate(resolve));
const deferred = () => {
let resolve;
const promise = new Promise(yes => { resolve = yes; });
return {promise, resolve};
};
async function fixture(t, control) {
const bot = new EventEmitter();
bot.inventory = {slots: [{name: 'stone'}, null], selectedItem: null};
const calls = [];
const workflow = {control(request) {
calls.push(request);
return control?.(request) ?? {...request, workflow_id: request.workflow_id ?? null, state: 'accepted'};
}};
const context = {bot_instance: bot, bot_status: 'online', workflow_manager: workflow};
const manager = await create_function_manager(context);
t.after(() => manager.dispose());
return {bot, manager, context, workflow, calls};
}
test('discovery publishes six Chinese workflow functions without exposing execution metadata', async () => {
const category = (await get_function_list()).find(item => item.path === 'workflow');
assert.equal(category.name, 'workflow');
assert.equal(category.i18n[0].lang, 'zh-CN');
assert.ok(category.i18n[0].name && category.i18n[0].description);
assert.deepEqual(category.function_list.map(fn => fn.name).sort(), [...names].sort());
for (const fn of category.function_list) {
assert.equal(fn.i18n.lang, 'zh-CN');
assert.ok(fn.i18n.name && fn.i18n.description);
assert.deepEqual(fn.args.map(arg => [arg.name, arg.type]), [[fn.name === 'stop_run' ? 'run_id' : 'workflow_id', 'string']]);
assert.equal(fn.args[0].i18n.lang, 'zh-CN');
assert.ok(fn.args[0].i18n.name && fn.args[0].i18n.description);
assert.equal(Object.hasOwn(fn, 'execution'), false);
assert.equal(Object.hasOwn(fn.args[0], 'default'), false);
assert.equal(operations[fn.name]().execution, 'workflow_control');
}
});
test('each invocation submits one structured control request with a fresh UUID and no input', async t => {
const {manager, calls} = await fixture(t);
let checks = 0;
for (const name of [...names, ...names]) {
const id = name === 'stop_run' ? 'a347a828-fcdd-4ff5-ae88-c20ea39ca749' : 'workflow_1-x';
assert.equal(await manager.run('workflow', name, [id], () => checks++), true);
const request = calls.at(-1);
const key = name === 'stop_run' ? 'run_id' : 'workflow_id';
assert.deepEqual(Object.keys(request).sort(), ['action', 'command_id', key].sort());
assert.equal(request.action, name);
assert.equal(request[key], id);
assert.match(request.command_id, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
}
assert.equal(new Set(calls.map(request => request.command_id)).size, 12);
assert.equal(checks, 12);
});
test('long ordinary execution and a full ordinary queue cannot block offline workflow control', async t => {
const {bot, manager, context, calls} = await fixture(t);
const click = deferred();
t.after(() => click.resolve());
const clicks = [];
bot.clickWindow = slot => { clicks.push(slot); return click.promise; };
let finished = false;
const running = manager.run('inventory', 'slot_to_slot', [0, 1]).then(result => { finished = true; return result; });
await turn();
assert.deepEqual(clicks, [0]);
let queuedChecks = 0;
const queued = Array.from({length: 64}, () => manager.run('inventory', 'slot_to_slot', [0, 1], () => queuedChecks++));
await assert.rejects(manager.run('inventory', 'slot_to_slot', [0, 1]), /队列已满/);
context.bot_status = 'disconnecting';
for (const name of ['stop', 'stop_run', 'reload']) {
const result = manager.run('workflow', name, ['target']);
assert.equal(calls.at(-1).action, name, 'control dispatches synchronously');
assert.equal(await Promise.race([result, turn().then(() => 'blocked')]), true);
}
assert.equal(finished, false);
assert.equal(queuedChecks, 0);
await assert.rejects(manager.run('bot', 'jump', []), /不在线/);
context.bot_status = 'online';
click.resolve();
assert.equal(await running, true);
assert.deepEqual(await Promise.all(queued), Array(64).fill(true));
assert.equal(queuedChecks, 64);
});
test('self-stop acknowledges before its own function node can finish and cancellation drains independently', async t => {
const controller = new AbortController();
const ownNode = deferred();
let stopFinished = false;
const {manager, calls} = await fixture(t, request => {
assert.equal(request.run_id, 'current_run');
// The stop task waits for this very function node, as the real runtime does.
void ownNode.promise.then(() => { controller.abort(); stopFinished = true; });
return {...request, workflow_id: 'self', state: 'accepted'};
});
const result = manager.run('workflow', 'stop_run', ['current_run'], undefined, {signal: controller.signal});
assert.equal(await Promise.race([result, turn().then(() => 'blocked')]), true);
assert.equal(calls.length, 1);
assert.equal(stopFinished, false);
assert.equal(getEventListeners(controller.signal, 'abort').length, 0);
ownNode.resolve();
await turn();
assert.equal(stopFinished, true);
});
test('a forged workflow_control flag on a non-workflow function cannot bypass the queue or online check', async t => {
// Simulate an ordinary loaded class declaring the same flag, without changing source files.
Object.defineProperty(Object.prototype, 'execution', {value: 'workflow_control', configurable: true});
try {
const {bot, manager, context, calls} = await fixture(t);
const click = deferred();
t.after(() => click.resolve());
bot.clickWindow = () => click.promise;
const running = manager.run('inventory', 'slot_to_slot', [0, 1]);
await turn();
let before = 0;
const queued = manager.run('inventory', 'slot_to_slot', [0, 1], () => before++);
await turn();
assert.equal(before, 0);
assert.deepEqual(calls, []);
context.bot_status = 'offline';
await assert.rejects(manager.run('bot', 'jump', []), /不在线/);
context.bot_status = 'online';
click.resolve();
assert.equal(await running, true);
assert.equal(await queued, true);
assert.equal(before, 1);
} finally { delete Object.prototype.execution; }
});
test('missing manager, rejected commands and control exceptions return false', async t => {
const {manager, context, workflow, calls} = await fixture(t);
for (const missing of [null, undefined]) {
context.workflow_manager = missing;
for (const name of names) assert.equal(await manager.run('workflow', name, ['target']), false);
}
context.workflow_manager = workflow;
workflow.control = () => { throw Error('not ready'); };
assert.equal(await manager.run('workflow', 'stop', ['target']), false);
workflow.control = () => ({state: 'failed', error: {code: 'NOT_FOUND', message: 'missing'}});
assert.equal(await manager.run('workflow', 'stop', ['target']), false);
workflow.control = () => ({state: 'completed'});
assert.equal(await manager.run('workflow', 'stop', ['target']), true);
assert.deepEqual(calls, []);
for (const name of names) assert.equal(await operations[name]().run(null, ['target']), false);
});
test('IDs reject malformed values and injection strings without calling control', async t => {
const {manager, calls} = await fixture(t);
for (const name of names) {
const fn = operations[name]();
const execution = {workflow: {control() { assert.fail('invalid input reached control'); }}};
for (const args of [null, [], [1], [{}], ['target', {}], ['target', 'extra']])
assert.equal(await fn.run(null, args, execution), false);
for (const id of ['', ' ', '../target', 'a/b', 'a.b', 'a b', 'x'.repeat(101), '__proto__', 'prototype',
'constructor', 'target\n', 'target\0', '${process.exit()}', '";process.exit();//']) {
assert.equal(await fn.run(null, [id], execution), false);
assert.equal(await manager.run('workflow', name, [id]), false);
}
for (const args of [[], [1], ['target', {}]])
await assert.rejects(manager.run('workflow', name, args), /参数/);
}
assert.deepEqual(calls, []);
});
test('permission callback, signal and manager or Bot changes prevent stale control dispatch', async t => {
for (const change of ['deny', 'abort', 'dispose', 'bot', 'manager']) {
await t.test(change, async t => {
const {manager, context, calls} = await fixture(t);
const controller = new AbortController();
const denied = Error('permission denied');
await assert.rejects(manager.run('workflow', 'stop', ['target'], () => {
if (change === 'deny') throw denied;
if (change === 'abort') controller.abort(denied);
if (change === 'dispose') manager.dispose();
if (change === 'bot') context.bot_instance = {};
if (change === 'manager') context.workflow_manager = {control() { assert.fail('replacement used'); }};
}, {signal: controller.signal}), change === 'deny' || change === 'abort' ? error => error === denied : undefined);
assert.deepEqual(calls, []);
});
}
const {manager, calls} = await fixture(t);
const controller = new AbortController();
controller.abort();
await assert.rejects(manager.run('workflow', 'stop', ['target'], () => assert.fail('pre-aborted callback'),
{signal: controller.signal}), {name: 'AbortError'});
assert.deepEqual(calls, []);
});
test('direct control checks cancellation, disposal and identity again after execution', async t => {
for (const change of ['abort', 'dispose', 'bot', 'manager']) {
await t.test(change, async t => {
const {manager, context, workflow, calls} = await fixture(t);
const controller = new AbortController();
workflow.control = request => {
calls.push(request);
if (change === 'abort') controller.abort();
if (change === 'dispose') manager.dispose();
if (change === 'bot') context.bot_instance = null;
if (change === 'manager') context.workflow_manager = null;
return {...request, state: 'accepted'};
};
await assert.rejects(manager.run('workflow', 'stop', ['target'], undefined, {signal: controller.signal}));
assert.equal(calls.length, 1);
assert.equal(getEventListeners(controller.signal, 'abort').length, 0);
});
}
});
test('execution context exposes only a bound control capability and rejects later manager replacement', async t => {
const original = WorkflowControlFunction.prototype.run;
let execution;
t.mock.method(WorkflowControlFunction.prototype, 'run', function(bot, args, context) {
execution = context;
return original.call(this, bot, args, context);
});
const {manager, context, workflow, calls} = await fixture(t);
workflow.control = function(request) {
assert.equal(this, workflow);
calls.push(request);
return {...request, state: 'accepted'};
};
assert.equal(await manager.run('workflow', 'reload', ['target']), true);
assert.deepEqual(Object.keys(execution), ['workflow']);
assert.deepEqual(Object.keys(execution.workflow), ['control']);
assert.notEqual(execution.workflow, workflow);
context.workflow_manager = null;
assert.throws(() => execution.workflow.control({command_id: 'stale', action: 'stop', workflow_id: 'target'}), /已变化/);
assert.equal(calls.length, 1);
});