159 lines
5.5 KiB
TypeScript
159 lines
5.5 KiB
TypeScript
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';
|
|
import { PlatformCommands } from '../../YouleNexus/assets/framework/platform/commands.ts';
|
|
import { PlatformStore } from '../../YouleNexus/assets/framework/platform/stores/platform-store.ts';
|
|
import { parseLoginResponse } from '../../YouleNexus/assets/framework/protocol/contracts/index.ts';
|
|
import type {
|
|
GameEntry,
|
|
GameHost,
|
|
GameModule,
|
|
} from '../../YouleNexus/assets/framework/sdk/contracts/index.ts';
|
|
import {
|
|
assertGameContract,
|
|
type GameContractHost,
|
|
} from '../../YouleNexus/assets/framework/sdk/testing/game-contract-harness.ts';
|
|
|
|
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<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', () => {
|
|
let invalidations = 0;
|
|
let nextModuleId = 0;
|
|
const restoredDeskinfo: unknown[] = [];
|
|
const entry: GameEntry = {
|
|
key: 'fixture',
|
|
gameId: 41,
|
|
route: 'fixture-route',
|
|
resolveSeatCount: () => 4,
|
|
createModule: () => makeModule(++nextModuleId, (deskinfo) => { restoredDeskinfo.push(deskinfo); }),
|
|
};
|
|
|
|
assert.doesNotThrow(() => assertGameContract(
|
|
entry,
|
|
[{
|
|
roomtype: GAME_ROOMTYPE,
|
|
expectedSeatCount: 4,
|
|
makeHost: makeConformanceHost(entry, () => { invalidations++; }, restoredDeskinfo),
|
|
}],
|
|
assert,
|
|
));
|
|
assert.equal(invalidations, 2);
|
|
assert.equal(restoredDeskinfo.length, 0);
|
|
assert.ok(nextModuleId >= 2);
|
|
});
|
|
|
|
const RUNTIME_CONFIG: RuntimeConfig = Object.freeze({
|
|
mode: 'release',
|
|
isDebugger: false,
|
|
identity: Object.freeze({
|
|
agentid: 'fixture-agent', channelid: 'fixture-channel', gameid: '41',
|
|
marketid: 'fixture-market', version: 41,
|
|
}),
|
|
servers: Object.freeze(['ws://fixture']),
|
|
source: 'remote',
|
|
gameserver: 'https://fixture/config',
|
|
rawConfig: Object.freeze({ data: Object.freeze({ urlserver: 'fixture' }) }),
|
|
});
|
|
|
|
function makeModule(id: number, onRestore: (deskinfo: unknown) => void): GameModule {
|
|
let host: GameHost | undefined;
|
|
let restored: unknown;
|
|
let eventCount = 0;
|
|
let messageCycle: number | undefined;
|
|
return {
|
|
attach(value) { host = value; },
|
|
handlePlatformEvent(event) {
|
|
eventCount++;
|
|
if (event.type === 'room.entered') {
|
|
assert.deepEqual(event.roomtype, GAME_ROOMTYPE);
|
|
assert.deepEqual(host!.getSnapshot().room.roomtype, event.roomtype);
|
|
}
|
|
},
|
|
handleGameMessage(message) {
|
|
assert.equal(message.rpc, 'conformance.message');
|
|
const data = message.data as { cycle: number };
|
|
assert.equal(typeof data.cycle, 'number');
|
|
messageCycle = data.cycle;
|
|
},
|
|
restore(deskinfo) {
|
|
restored = deskinfo;
|
|
onRestore(deskinfo);
|
|
},
|
|
dispose() {
|
|
if (!host) {
|
|
assert.equal(eventCount, 0);
|
|
assert.equal(restored, undefined);
|
|
return;
|
|
}
|
|
assert.ok(host);
|
|
assert.equal(eventCount, 1);
|
|
assert.deepEqual(restored, deskinfoFor(messageCycle!));
|
|
},
|
|
};
|
|
}
|
|
|
|
function deskinfoFor(cycle: number): { readonly cycle: number } {
|
|
return { cycle };
|
|
}
|
|
|
|
function makeConformanceHost(
|
|
entry: GameEntry,
|
|
onInvalidate: () => void,
|
|
restoredDeskinfo: unknown[],
|
|
): () => GameContractHost {
|
|
return () => {
|
|
const login = JSON.parse(readFileSync(
|
|
new URL('../fixtures/contracts/player-login-room.json', import.meta.url),
|
|
'utf8',
|
|
)) as { data: Record<string, unknown> };
|
|
login.data.isbattle = 0;
|
|
const store = new PlatformStore();
|
|
store.applyLoginSuccess(parseLoginResponse(login.data));
|
|
const sent: OutboundEnvelope[] = [];
|
|
const send = { send: (envelope: OutboundEnvelope) => { sent.push(envelope); } };
|
|
const commands = new PlatformCommands({ runtimeConfig: RUNTIME_CONFIG, store, send });
|
|
const lease = createGameHostAdapter({ gameEntry: entry, store, commands, send });
|
|
return {
|
|
host: lease.host,
|
|
invalidate() {
|
|
lease.invalidate();
|
|
onInvalidate();
|
|
},
|
|
assertRestored(deskinfo) {
|
|
assert.equal(restoredDeskinfo.shift(), deskinfo);
|
|
},
|
|
};
|
|
};
|
|
}
|