fix(sdk): inject assertions into Creator-loaded contract harness
This commit is contained in:
@@ -38,7 +38,9 @@ SDK runtime 也不导入框架实现层,平台适配由 `platform/game-host-ad
|
|||||||
|
|
||||||
`GameEntry.resolveSeatCount` 只接收真实房间的 `roomtype`,预检只检查该方法存在;
|
`GameEntry.resolveSeatCount` 只接收真实房间的 `roomtype`,预检只检查该方法存在;
|
||||||
GameHost 在进房边界验证返回值是正整数并与服务器座位数组长度相同。
|
GameHost 在进房边界验证返回值是正整数并与服务器座位数组长度相同。
|
||||||
`assertGameContract(entry, samples)` 的每个样本由游戏提供 `roomtype`、`expectedSeatCount`
|
`assertGameContract(entry, samples, assert)` 的 `assert` 由测试调用方从 `node:assert/strict`
|
||||||
|
显式导入并传入;harness 仅引用断言类型,避免 Creator 加载脚本时解析 Node 内置模块。
|
||||||
|
每个样本由游戏提供 `roomtype`、`expectedSeatCount`
|
||||||
和对应的 `makeHost` 工厂,不要求游戏支持框架虚构的房间配置。
|
和对应的 `makeHost` 工厂,不要求游戏支持框架虚构的房间配置。
|
||||||
|
|
||||||
远程地址唯一取自 `data.urlserver`。配置请求使用空 body 的 POST,沿用无条件 `?` 缓存参数拼接。
|
远程地址唯一取自 `data.urlserver`。配置请求使用空 body 的 POST,沿用无条件 `?` 缓存参数拼接。
|
||||||
|
|||||||
+10
-5
@@ -1,4 +1,4 @@
|
|||||||
import { strict as assert } from 'node:assert';
|
import type { strict } from 'node:assert';
|
||||||
import type {
|
import type {
|
||||||
GameEntry,
|
GameEntry,
|
||||||
GameHost,
|
GameHost,
|
||||||
@@ -21,8 +21,12 @@ export interface GameContractSample {
|
|||||||
readonly makeHost: GameContractHostFactory;
|
readonly makeHost: GameContractHostFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Runs the shared lifecycle checks required of the app's sole GameEntry. */
|
/** Runs lifecycle checks with assertions explicitly supplied by the test caller. */
|
||||||
export function assertGameContract(entry: GameEntry, samples: readonly GameContractSample[]): void {
|
export function assertGameContract(
|
||||||
|
entry: GameEntry,
|
||||||
|
samples: readonly GameContractSample[],
|
||||||
|
assert: typeof strict,
|
||||||
|
): void {
|
||||||
assert.ok(samples.length > 0, 'conformance requires game-owned roomtype samples');
|
assert.ok(samples.length > 0, 'conformance requires game-owned roomtype samples');
|
||||||
assertGameEntry(entry);
|
assertGameEntry(entry);
|
||||||
for (const route of ['platform', 'agent', 'room']) {
|
for (const route of ['platform', 'agent', 'room']) {
|
||||||
@@ -48,8 +52,8 @@ export function assertGameContract(entry: GameEntry, samples: readonly GameContr
|
|||||||
|
|
||||||
const sessions = new GameSessionHost(entry);
|
const sessions = new GameSessionHost(entry);
|
||||||
for (const sample of samples) {
|
for (const sample of samples) {
|
||||||
runCycle(sessions, sample, entry, 1);
|
runCycle(sessions, sample, entry, 1, assert);
|
||||||
runCycle(sessions, sample, entry, 2);
|
runCycle(sessions, sample, entry, 2, assert);
|
||||||
}
|
}
|
||||||
assert.equal(sessions.state, 'idle');
|
assert.equal(sessions.state, 'idle');
|
||||||
}
|
}
|
||||||
@@ -59,6 +63,7 @@ function runCycle(
|
|||||||
sample: GameContractSample,
|
sample: GameContractSample,
|
||||||
entry: GameEntry,
|
entry: GameEntry,
|
||||||
cycle: number,
|
cycle: number,
|
||||||
|
assert: typeof strict,
|
||||||
): void {
|
): void {
|
||||||
const contractHost = sample.makeHost();
|
const contractHost = sample.makeHost();
|
||||||
const snapshot = contractHost.host.getSnapshot();
|
const snapshot = contractHost.host.getSnapshot();
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { test } from 'node:test';
|
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 { RuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
|
||||||
import type { OutboundEnvelope } from '../../YouleNexus/assets/framework/core/types/envelope.ts';
|
import type { OutboundEnvelope } from '../../YouleNexus/assets/framework/core/types/envelope.ts';
|
||||||
import { createGameHostAdapter } from '../../YouleNexus/assets/framework/platform/game-host-adapter.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',
|
new URL('../fixtures/contracts/player-login-room.json', import.meta.url), 'utf8',
|
||||||
)).data.roomtype as readonly unknown[];
|
)).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<string, unknown> = {};
|
||||||
|
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', () => {
|
test('assertGameContract accepts a fixed-four game with its real nested roomtype and host fixture', () => {
|
||||||
let invalidations = 0;
|
let invalidations = 0;
|
||||||
let nextModuleId = 0;
|
let nextModuleId = 0;
|
||||||
@@ -40,6 +66,7 @@ test('assertGameContract accepts a fixed-four game with its real nested roomtype
|
|||||||
expectedSeatCount: 4,
|
expectedSeatCount: 4,
|
||||||
makeHost: makeConformanceHost(entry, () => { invalidations++; }, restoredDeskinfo),
|
makeHost: makeConformanceHost(entry, () => { invalidations++; }, restoredDeskinfo),
|
||||||
}],
|
}],
|
||||||
|
assert,
|
||||||
));
|
));
|
||||||
assert.equal(invalidations, 2);
|
assert.equal(invalidations, 2);
|
||||||
assert.equal(restoredDeskinfo.length, 0);
|
assert.equal(restoredDeskinfo.length, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user