Files
cc-3-8-x-mcp/router/test/offline-tools.test.js
T
furao a3b016ea8e [cc-3-8-x-mcp] 修复: 补全离线 CLI 解析路径与自包含夹具
改了什么:解析结果记录资源来源路径,并生成独立 Prefab、嵌套资源和脚本夹具。

为什么:测试依赖外部项目资源和临时目录时会导致嵌套解析与类 ID 结果不稳定。

影响范围:离线 CLI 解析、组件引用错误信息及 offline tools 测试。
2026-08-14 12:09:20 +08:00

217 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// ============================================================
// router/test/offline-tools.test.js
// T13 offline tool 测试
//
// 直接 require router/src/offline-tools.js,测:
// 1. prefab_query happy pathtree / node / find
// 2. prefab_edit happy pathset-active 写 tmp 文件)
// 3. prefab_batch happy pathopsJson 文件 → editPrefab
// 4. 相对路径 filePath 报错
// 5. prefab_batch opsJsonPath 相对路径报错
// ============================================================
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const os = require('os');
const {
isOfflineTool,
handleOfflineToolCall,
requireAbsolutePath,
OFFLINE_TOOLS,
} = require('../src/offline-tools.js');
// fixture: HomeUI.prefab(只读,在 cli/test/fixtures/
const fixture = require('../../cli/test/fixture.js');
const FIXTURE_PATH = fixture.ensureHomeUiFixture();
// 复制 fixture 到 tmp 用于写操作
function makeTmp(tag) {
var dst = path.join(fixture.FIXTURE_TEMP_DIR, 'HomeUI-router-' + tag + '-' + process.pid + '-' + Date.now() + '.prefab');
fs.copyFileSync(FIXTURE_PATH, dst);
return dst;
}
// ── OFFLINE_TOOLS 定义完整性 ────────────────────────────────────
test('OFFLINE_TOOLS 导出 3 个 tool,名称正确', () => {
assert.equal(OFFLINE_TOOLS.length, 3);
var names = OFFLINE_TOOLS.map(function (t) { return t.name; });
assert.ok(names.includes('prefab_query'));
assert.ok(names.includes('prefab_edit'));
assert.ok(names.includes('prefab_batch'));
});
test('isOfflineTool 对已知 name 返回 true,未知 name 返回 false', () => {
assert.equal(isOfflineTool('prefab_query'), true);
assert.equal(isOfflineTool('prefab_edit'), true);
assert.equal(isOfflineTool('prefab_batch'), true);
assert.equal(isOfflineTool('gateway_list_editors'), false);
assert.equal(isOfflineTool('scene_set_property'), false);
assert.equal(isOfflineTool(''), false);
});
test('每个 offline tool description 包含 "[offline]" 标注', () => {
for (var t of OFFLINE_TOOLS) {
assert.ok(
t.description.includes('[offline]'),
'tool ' + t.name + ' description 应包含 "[offline]"'
);
}
});
// ── requireAbsolutePath ────────────────────────────────────────
test('requireAbsolutePath 相对路径抛错', () => {
assert.throws(
function () { requireAbsolutePath('relative/path.prefab', 'test'); },
/必须是绝对路径/
);
});
test('requireAbsolutePath 绝对路径不抛错', () => {
assert.doesNotThrow(function () {
requireAbsolutePath('/absolute/path.prefab', 'test');
});
});
// ── prefab_query happy path ─────────────────────────────────────
test('prefab_query type=tree 返回 MCP content,根节点名称为 HomeUI', async () => {
var result = await handleOfflineToolCall('prefab_query', {
filePath: FIXTURE_PATH,
selector: { type: 'tree' },
});
assert.ok(Array.isArray(result.content), 'result.content 应是数组');
assert.equal(result.content[0].type, 'text');
var data = JSON.parse(result.content[0].text);
assert.equal(data.name, 'HomeUI', '根节点 name 应为 HomeUI');
assert.ok(Array.isArray(data.children), 'children 应是数组');
});
test('prefab_query 无 selector 默认返回 tree', async () => {
var result = await handleOfflineToolCall('prefab_query', {
filePath: FIXTURE_PATH,
});
var data = JSON.parse(result.content[0].text);
assert.equal(data.name, 'HomeUI');
});
test('prefab_query type=find 返回 cc.Label id 列表', async () => {
var result = await handleOfflineToolCall('prefab_query', {
filePath: FIXTURE_PATH,
selector: { type: 'find', nodeType: 'cc.Label' },
});
var ids = JSON.parse(result.content[0].text);
assert.ok(Array.isArray(ids), 'find 结果应是数组');
assert.ok(ids.length > 0, '应找到至少一个 cc.Label');
ids.forEach(function (id) { assert.equal(typeof id, 'number'); });
});
// ── prefab_query 相对路径报错 ────────────────────────────────────
test('prefab_query 相对路径 filePath 抛错', async () => {
await assert.rejects(
function () {
return handleOfflineToolCall('prefab_query', {
filePath: 'relative/HomeUI.prefab',
});
},
/必须是绝对路径/
);
});
// ── prefab_edit happy path ──────────────────────────────────────
test('prefab_edit set-active 成功,返回 changed=true + opsApplied=1', async () => {
var tmp = makeTmp('edit');
try {
var result = await handleOfflineToolCall('prefab_edit', {
filePath: tmp,
ops: [
{ op: 'set-active', node: 'HomeUI', active: false },
],
});
var data = JSON.parse(result.content[0].text);
assert.equal(data.changed, true, 'changed 应为 true');
assert.equal(data.opsApplied, 1, 'opsApplied 应为 1');
assert.ok(Array.isArray(data.nodesAffected), 'nodesAffected 应是数组');
} finally {
try { fs.unlinkSync(tmp); } catch (_) {}
}
});
// ── prefab_edit 相对路径报错 ─────────────────────────────────────
test('prefab_edit 相对路径 filePath 抛错', async () => {
await assert.rejects(
function () {
return handleOfflineToolCall('prefab_edit', {
filePath: './relative.prefab',
ops: [{ op: 'set-active', node: 'HomeUI', active: false }],
});
},
/必须是绝对路径/
);
});
// ── prefab_batch happy path ─────────────────────────────────────
test('prefab_batch 从 JSON 文件读取 ops,成功写回', async () => {
var tmp = makeTmp('batch');
var opsJson = path.join(os.tmpdir(), 'router-batch-ops-' + Date.now() + '.json');
var ops = [
{ op: 'set-active', node: 'HomeUI', active: true },
];
fs.writeFileSync(opsJson, JSON.stringify(ops), 'utf-8');
try {
var result = await handleOfflineToolCall('prefab_batch', {
filePath: tmp,
opsJsonPath: opsJson,
});
var data = JSON.parse(result.content[0].text);
assert.equal(data.changed, true);
assert.equal(data.opsApplied, 1);
} finally {
try { fs.unlinkSync(tmp); } catch (_) {}
try { fs.unlinkSync(opsJson); } catch (_) {}
}
});
// ── prefab_batch 相对路径报错 ────────────────────────────────────
test('prefab_batch 相对路径 filePath 抛错', async () => {
await assert.rejects(
function () {
return handleOfflineToolCall('prefab_batch', {
filePath: 'relative.prefab',
opsJsonPath: '/absolute/ops.json',
});
},
/必须是绝对路径/
);
});
test('prefab_batch 相对路径 opsJsonPath 抛错', async () => {
await assert.rejects(
function () {
return handleOfflineToolCall('prefab_batch', {
filePath: FIXTURE_PATH,
opsJsonPath: 'relative/ops.json',
});
},
/必须是绝对路径/
);
});