import { remoteFixture } from '../fixtures/remote-config.ts'; 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 = {}; 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', ...remoteFixture(), }); 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 }; 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); }, }; }; }