From 266be19e76e70779c9af53c6ea2d607e4ccb2bd0 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sat, 5 Sep 2026 18:58:44 +0800 Subject: [PATCH] fix(sdk): inject assertions into Creator-loaded contract harness --- .../YouleNexus/assets/framework/README.md | 4 ++- .../sdk/testing/game-contract-harness.ts | 15 +++++++---- .../sdk/game-contract-harness.test.ts | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/cocoscreator_projects/YouleNexus/assets/framework/README.md b/cocoscreator_projects/YouleNexus/assets/framework/README.md index fd57e0d..cbbd344 100644 --- a/cocoscreator_projects/YouleNexus/assets/framework/README.md +++ b/cocoscreator_projects/YouleNexus/assets/framework/README.md @@ -38,7 +38,9 @@ SDK runtime 也不导入框架实现层,平台适配由 `platform/game-host-ad `GameEntry.resolveSeatCount` 只接收真实房间的 `roomtype`,预检只检查该方法存在; GameHost 在进房边界验证返回值是正整数并与服务器座位数组长度相同。 -`assertGameContract(entry, samples)` 的每个样本由游戏提供 `roomtype`、`expectedSeatCount` +`assertGameContract(entry, samples, assert)` 的 `assert` 由测试调用方从 `node:assert/strict` +显式导入并传入;harness 仅引用断言类型,避免 Creator 加载脚本时解析 Node 内置模块。 +每个样本由游戏提供 `roomtype`、`expectedSeatCount` 和对应的 `makeHost` 工厂,不要求游戏支持框架虚构的房间配置。 远程地址唯一取自 `data.urlserver`。配置请求使用空 body 的 POST,沿用无条件 `?` 缓存参数拼接。 diff --git a/cocoscreator_projects/YouleNexus/assets/framework/sdk/testing/game-contract-harness.ts b/cocoscreator_projects/YouleNexus/assets/framework/sdk/testing/game-contract-harness.ts index 1a3bfd2..7c80bb3 100644 --- a/cocoscreator_projects/YouleNexus/assets/framework/sdk/testing/game-contract-harness.ts +++ b/cocoscreator_projects/YouleNexus/assets/framework/sdk/testing/game-contract-harness.ts @@ -1,4 +1,4 @@ -import { strict as assert } from 'node:assert'; +import type { strict } from 'node:assert'; import type { GameEntry, GameHost, @@ -21,8 +21,12 @@ export interface GameContractSample { readonly makeHost: GameContractHostFactory; } -/** Runs the shared lifecycle checks required of the app's sole GameEntry. */ -export function assertGameContract(entry: GameEntry, samples: readonly GameContractSample[]): void { +/** Runs lifecycle checks with assertions explicitly supplied by the test caller. */ +export function assertGameContract( + entry: GameEntry, + samples: readonly GameContractSample[], + assert: typeof strict, +): void { assert.ok(samples.length > 0, 'conformance requires game-owned roomtype samples'); assertGameEntry(entry); for (const route of ['platform', 'agent', 'room']) { @@ -48,8 +52,8 @@ export function assertGameContract(entry: GameEntry, samples: readonly GameContr const sessions = new GameSessionHost(entry); for (const sample of samples) { - runCycle(sessions, sample, entry, 1); - runCycle(sessions, sample, entry, 2); + runCycle(sessions, sample, entry, 1, assert); + runCycle(sessions, sample, entry, 2, assert); } assert.equal(sessions.state, 'idle'); } @@ -59,6 +63,7 @@ function runCycle( sample: GameContractSample, entry: GameEntry, cycle: number, + assert: typeof strict, ): void { const contractHost = sample.makeHost(); const snapshot = contractHost.host.getSnapshot(); diff --git a/cocoscreator_projects/framework-tests/sdk/game-contract-harness.test.ts b/cocoscreator_projects/framework-tests/sdk/game-contract-harness.test.ts index 8e0531f..1b88668 100644 --- a/cocoscreator_projects/framework-tests/sdk/game-contract-harness.test.ts +++ b/cocoscreator_projects/framework-tests/sdk/game-contract-harness.test.ts @@ -1,6 +1,9 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { test } from 'node:test'; +import { runInNewContext } from 'node:vm'; +import ts from 'typescript'; +import * as gameSessionHost from '../../YouleNexus/assets/framework/sdk/runtime/game-session-host.ts'; import type { RuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts'; import type { OutboundEnvelope } from '../../YouleNexus/assets/framework/core/types/envelope.ts'; import { createGameHostAdapter } from '../../YouleNexus/assets/framework/platform/game-host-adapter.ts'; @@ -21,6 +24,29 @@ const GAME_ROOMTYPE = JSON.parse(readFileSync( new URL('../fixtures/contracts/player-login-room.json', import.meta.url), 'utf8', )).data.roomtype as readonly unknown[]; +test('browser-like loader evaluates the emitted harness without Node builtin modules', () => { + const source = readFileSync(new URL( + '../../YouleNexus/assets/framework/sdk/testing/game-contract-harness.ts', import.meta.url, + ), 'utf8'); + const { outputText } = ts.transpileModule(source, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, + }); + const exports: Record = {}; + const loaded: string[] = []; + assert.doesNotThrow(() => runInNewContext(outputText, { + exports, + require(specifier: string) { + if (specifier !== '../runtime/game-session-host.ts') { + throw new Error(`Creator cannot load runtime module: ${specifier}`); + } + loaded.push(specifier); + return gameSessionHost; + }, + })); + assert.equal(typeof exports.assertGameContract, 'function'); + assert.deepEqual(loaded, ['../runtime/game-session-host.ts']); +}); + test('assertGameContract accepts a fixed-four game with its real nested roomtype and host fixture', () => { let invalidations = 0; let nextModuleId = 0; @@ -40,6 +66,7 @@ test('assertGameContract accepts a fixed-four game with its real nested roomtype expectedSeatCount: 4, makeHost: makeConformanceHost(entry, () => { invalidations++; }, restoredDeskinfo), }], + assert, )); assert.equal(invalidations, 2); assert.equal(restoredDeskinfo.length, 0);