import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { once } from 'node:events'; import fs from 'node:fs'; import net from 'node:net'; import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; const nativeRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', ); const repoRoot = path.resolve(nativeRoot, '../../../..'); const guidePath = path.join( repoRoot, 'apps/ai-game-creator-shell/src-tauri/resources/agc-skills/agc-godot-editor/references/【操作指南】Godot编辑器常用操作-2026-09-20.md', ); const guide = fs.readFileSync(guidePath, 'utf8'); const examples = new Map( [ ...guide.matchAll( /\r?\n```gdscript\r?\n([\s\S]*?)\r?\n```/g, ), ].map((match) => [match[1], match[2]]), ); const executable = process.env.AGC_GODOT_TEST_EXECUTABLE; const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function until(predicate, duration = 15000) { const end = Date.now() + duration; while (Date.now() < end) { const value = predicate(); if (value) return value; await pause(25); } throw Error('Godot guide fixture did not become ready'); } function request(session, method, params = {}) { return new Promise((resolve, reject) => { const socket = net.createConnection({ host: '127.0.0.1', port: session.port, }); let data = ''; socket.setTimeout(10000, () => socket.destroy(Error('Guide execution receipt timed out')), ); socket.once('error', reject); socket.once('connect', () => socket.write( `${JSON.stringify({ protocol: session.protocol, id: 1, generation: session.generation, token: session.token, method, params, })}\n`, ), ); socket.on('data', (chunk) => { data += chunk; const newline = data.indexOf('\n'); if (newline < 0) return; try { const reply = JSON.parse(data.slice(0, newline)); assert.equal(reply.protocol, session.protocol); assert.equal(reply.generation, session.generation); assert.equal(reply.pid, session.pid); assert.equal(reply.buildId, session.buildId); assert.equal( path.resolve(reply.projectPath).toLowerCase(), path.resolve(session.projectPath).toLowerCase(), ); resolve(reply.result); } catch (error) { reject(error); } socket.end(); }); socket.once('end', () => { if (!data.includes('\n')) reject(Error('Godot exited without a receipt')); }); }); } test('Godot guide examples are unique, extractable, and within the runtime read budget', () => { assert.ok(Buffer.byteLength(guide, 'utf8') <= 14 * 1024); assert.equal([...guide.matchAll(/