feat(config): unify startup mode and server selection

This commit is contained in:
2026-09-05 22:52:14 +08:00
parent 239af0aa39
commit facceeae9b
26 changed files with 204 additions and 240 deletions
@@ -2,14 +2,14 @@ import { remoteFixture } from '../../fixtures/remote-config.ts';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { BUILD_IDENTITY } from '../../../YouleNexus/assets/framework/config/sources/defaults.ts';
import { PROFILES } from '../../../YouleNexus/assets/framework/config/profiles.ts';
import { STARTUP_CONFIG } from '../../../YouleNexus/assets/framework/config/profiles.ts';
import type { LocalStartupContext } from '../../../YouleNexus/assets/framework/config/local-startup.ts';
import { LOCAL_MACHINE_KEY, localAccountKey, type LocalStoragePort } from '../../../YouleNexus/assets/framework/adapters/local/local-storage.ts';
import { createLocalLoginSources, type LocalHostSnapshot } from '../../../YouleNexus/assets/framework/adapters/local/login-sources.ts';
const context: LocalStartupContext = { hostKind: 'h5', profile: 'local', config: {
mode: 'debug', isDebugger: true, source: 'remote', gameserver: PROFILES.local!.gameserver, ...remoteFixture(),
servers: PROFILES.local!.servers!, identity: BUILD_IDENTITY,
startupConfig: STARTUP_CONFIG, mode: 'debug', isDebugger: true, source: 'remote', gameserver: STARTUP_CONFIG.gameserver, ...remoteFixture(),
servers: STARTUP_CONFIG.localServers, identity: BUILD_IDENTITY,
} };
class MapStorage implements LocalStoragePort {
readonly values = new Map<string, string>();
@@ -2,7 +2,7 @@ import { remoteFixture } from '../../fixtures/remote-config.ts';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { BUILD_IDENTITY } from '../../../YouleNexus/assets/framework/config/sources/defaults.ts';
import { PROFILES } from '../../../YouleNexus/assets/framework/config/profiles.ts';
import { STARTUP_CONFIG } from '../../../YouleNexus/assets/framework/config/profiles.ts';
import type { LocalStartupContext } from '../../../YouleNexus/assets/framework/config/local-startup.ts';
import { LOCAL_MACHINE_KEY, localAccountKey, type LocalStoragePort } from '../../../YouleNexus/assets/framework/adapters/local/local-storage.ts';
import { createLocalVisitorProvider } from '../../../YouleNexus/assets/framework/adapters/local/visitor-account.ts';
@@ -16,8 +16,8 @@ class MapStorage implements LocalStoragePort {
function context(overrides: Partial<typeof BUILD_IDENTITY> = {}): LocalStartupContext {
return { hostKind: 'h5', profile: 'local', config: {
mode: 'debug', isDebugger: true, source: 'remote', gameserver: PROFILES.local!.gameserver, ...remoteFixture(),
servers: PROFILES.local!.servers!, identity: { ...BUILD_IDENTITY, ...overrides },
startupConfig: STARTUP_CONFIG, mode: 'debug', isDebugger: true, source: 'remote', gameserver: STARTUP_CONFIG.gameserver, ...remoteFixture(),
servers: STARTUP_CONFIG.localServers, identity: { ...BUILD_IDENTITY, ...overrides },
} };
}
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { resolveBootstrap } from '../../YouleNexus/assets/framework/config/bootstrap.ts';
@@ -8,9 +9,19 @@ test('resolveBootstrap:显式 hostKind 透传严格 RuntimeConfig,并保留
async fetch() { return { player_server_tcp: '5.5.5.5:5000' }; },
};
const result = await resolveBootstrap({
mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local', fetcher,
hostKind: 'h5', win: {}, search: '', fetcher,
});
assert.equal(result.source, 'remote');
assert.equal(result.profile.name, 'local');
assert.deepEqual(result.servers, ['ws://127.0.0.1:3088']);
});
test('facade forwards the entire injected source and derives its compatibility label', async () => {
for (const useLocalServer of [true,false]) {
const startupConfig={...STARTUP_CONFIG,mode:'release' as const,useLocalServer,gameserver:'https://fixture.example/config.txt'};
const result=await resolveBootstrap({startupConfig,isNative:false,win:{},search:'?mode=debug&profile=unknown&profile=local',fetcher:{async fetch(url){assert.equal(url,startupConfig.gameserver);return {player_server_tcp:'remote.example:3088'};}}});
assert.deepEqual(result.startupConfig,startupConfig); assert.equal(result.mode,'release');
assert.equal(result.profile.name,useLocalServer?'local':'prod');
assert.deepEqual(result.servers,useLocalServer?STARTUP_CONFIG.localServers:['ws://remote.example:3088']);
}
});
@@ -7,12 +7,12 @@ import {
resolveLocalStartup,
type LocalStartupContext,
} from '../../YouleNexus/assets/framework/config/local-startup.ts';
import { PROFILES } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import type { ResolveRuntimeConfigOptions, RuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
import type { ConfigFetcher } from '../../YouleNexus/assets/framework/config/remote-config.ts';
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
function options(search = '?profile=local'): ResolveRuntimeConfigOptions & {
function options(search = ''): ResolveRuntimeConfigOptions & {
fetcher: ConfigFetcher & { calls: number };
} {
const fetcher = {
@@ -22,7 +22,7 @@ function options(search = '?profile=local'): ResolveRuntimeConfigOptions & {
return { player_server_tcp: 'remote.example:3088' };
},
};
return { mode: 'debug', hostKind: 'h5', win: {}, search, fetcher };
return { hostKind: 'h5', win: {}, search, fetcher };
}
test('local debug fetches remote config before choosing authoritative local servers', async () => {
@@ -32,7 +32,7 @@ test('local debug fetches remote config before choosing authoritative local serv
assert.equal(context.hostKind, 'h5');
assert.equal(context.profile, 'local');
assert.equal(context.config.identity.gameid, BUILD_IDENTITY.gameid);
assert.deepEqual(context.config.servers, PROFILES.local!.servers);
assert.deepEqual(context.config.servers, STARTUP_CONFIG.localServers);
assert.equal(input.fetcher.calls, 1);
assert.equal(context.config.source, 'remote');
assert.equal(Object.isFrozen(context), true);
@@ -40,7 +40,8 @@ test('local debug fetches remote config before choosing authoritative local serv
test('rejects non-local startup modes and hosts before fetching', async () => {
const cases: Array<[Partial<ResolveRuntimeConfigOptions>, RegExp]> = [
[{ mode: 'release' }, /mode.*debug/i],
[{ startupConfig: {...STARTUP_CONFIG, mode:'release'} }, /mode.*debug/i],
[{ startupConfig: {...STARTUP_CONFIG, useLocalServer:false} }, /useLocalServer/i],
[{ hostKind: 'native-settings' }, /hostKind.*h5/i],
[{ hostKind: 'uAgent_3' }, /hostKind.*h5/i],
];
@@ -52,21 +53,11 @@ test('rejects non-local startup modes and hosts before fetching', async () => {
}
});
test('requires exactly one local profile and only an optional single debug mode', async () => {
const cases: Array<[string, RegExp]> = [
['', /profile/i],
['?profile=prod', /profile/i],
['?profile=staging', /profile/i],
['?profile=local&profile=local', /profile/i],
['?profile=local&mode=release', /mode.*debug/i],
['?profile=local&mode=debug&mode=debug', /mode/i],
];
for (const [search, expected] of cases) {
const input = options(search);
await assert.rejects(resolveLocalStartup(input), expected);
assert.equal(input.fetcher.calls, 0);
test('ignores conflicting and duplicate mode/profile queries', async () => {
for (const search of ['', '?profile=prod', '?profile=local&profile=unknown&mode=release&mode=other']) {
const input=options(search); const result=await resolveLocalStartup(input);
assert.deepEqual(result.config.servers,STARTUP_CONFIG.localServers); assert.equal(input.fetcher.calls,1);
}
await resolveLocalStartup(options('?profile=local&mode=debug'));
});
test('rejects gameconfig and ambiguous identity controls before fetching', async () => {
@@ -105,12 +96,13 @@ function validContext(): LocalStartupContext {
hostKind: 'h5',
profile: 'local',
config: {
startupConfig: STARTUP_CONFIG,
mode: 'debug',
isDebugger: true,
identity: BUILD_IDENTITY,
servers: PROFILES.local!.servers!,
servers: STARTUP_CONFIG.localServers,
source: 'remote',
gameserver: PROFILES.local!.gameserver,
gameserver: STARTUP_CONFIG.gameserver,
...remoteFixture(BUILD_IDENTITY),
},
};
@@ -172,3 +164,13 @@ test('connection target must be a valid URL from the authoritative local profile
assert.throws(() => assertLocalConnectionTarget(context, url), expected);
}
});
test('local consumers validate the injected source snapshot and its exact targets', async () => {
const startupConfig={...STARTUP_CONFIG,gameserver:'https://fixture.example/config.txt',localServers:['ws://[::1]:4300']};
const context=await resolveLocalStartup({...options(),startupConfig});
assertLocalConnectionTarget(context,'ws://[::1]:4300');
assert.throws(()=>assertLocalConnectionTarget(context,STARTUP_CONFIG.localServers[0]),/target/);
for (const patch of [{mode:'release'}, {useLocalServer:false}, {localServers:['ws://elsewhere:3088']}, {gameserver:'https://other.example/config.txt'}]) {
assert.throws(()=>assertLocalStartupContext({...context,config:{...context.config,startupConfig:{...context.config.startupConfig,...patch}}} as any),/mode|useLocalServer|localServers|gameserver/);
}
});
@@ -1,10 +1,9 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
ACTIVE_PROFILE,
PROFILES,
STARTUP_CONFIG,
validateStartupConfig,
decodeGameConfig,
resolveActiveProfile,
resolveGameServer,
} from '../../YouleNexus/assets/framework/config/profiles.ts';
@@ -23,19 +22,13 @@ test('resolveGameServer:空 gameconfig 使用配置源,非空值使用解码
);
});
test('默认 profile 是 ACTIVE_PROFILE,local 保持 remote 源与独立连接目标', () => {
assert.equal(resolveActiveProfile('').name, ACTIVE_PROFILE);
const local = resolveActiveProfile('?profile=local');
assert.equal(local.source, 'remote');
assert.ok(local.gameserver);
assert.deepEqual(local.servers, ['ws://127.0.0.1:3088']);
test('one frozen source defines the complete default startup configuration', () => {
assert.deepEqual(STARTUP_CONFIG, { mode: 'debug', gameserver: 'https://tsgames.daoqi88.cn/config/update_jsonv2.txt', useLocalServer: true, localServers: ['ws://127.0.0.1:3088'] });
assert.ok(Object.isFrozen(STARTUP_CONFIG)); assert.ok(Object.isFrozen(STARTUP_CONFIG.localServers));
});
test('未知 profile 显式报错,不回退到 ACTIVE_PROFILE', () => {
assert.throws(() => resolveActiveProfile('?profile=nope'), /未知 profile/);
});
test('正常 build 可达 profile 不含占位调试账号', () => {
assert.equal(JSON.stringify(PROFILES).includes('REPLACE_ME'), false);
assert.equal(PROFILES.staging.account, undefined);
test('source validation rejects malformed configuration without defaults', () => {
for (const patch of [{mode:'other'}, {useLocalServer:undefined}, {gameserver:'ws://host'}, {gameserver:''}, {localServers:[]}, {localServers:['ws://example.com:3088']}, {localServers:['ws://user:pass@localhost']}, {localServers:['ws://localhost/#fragment']}, {localServers:['ws://127.999.0.1']}, {localServers:['not-a-url']}]) {
assert.throws(() => validateStartupConfig({...STARTUP_CONFIG,...patch} as any), /startup config/i);
}
for (const url of ['ws://127.4.3.2:3088','wss://localhost:3088','ws://[::1]:3088']) assert.deepEqual(validateStartupConfig({...STARTUP_CONFIG,localServers:[url]}).localServers,[url]);
});
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { HttpConfigFetcher } from '../../YouleNexus/assets/framework/config/remote-config-fetcher.ts';
@@ -26,7 +27,7 @@ test('resolveRuntimeConfig:把 cacheBust 注入唯一的 HTTP 请求构造点'
});
await resolveRuntimeConfig({
mode: 'release',
startupConfig: {...STARTUP_CONFIG,mode:'release',useLocalServer:false},
hostKind: 'h5',
win: {},
search: '?agentid=A&channelid=C',
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
@@ -18,7 +19,7 @@ test('h5:只读 query 身份,不访问原生宿主,并生成严格 remote
const fetcher = returning(REMOTE);
const throwingSettings = new Proxy({}, { get() { throw new Error('native access forbidden'); } });
const result = await resolveRuntimeConfig({
mode: 'release',
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'h5',
win: { settings: throwingSettings },
search: '?agentid=HA&channelid=HC',
@@ -41,7 +42,7 @@ test('native-settings:使用四个原生方法,gameconfig 是唯一 gameserv
const calls: string[] = [];
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({
mode: 'release',
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'native-settings',
win: {
settings: {
@@ -65,7 +66,7 @@ test('native-settings:使用四个原生方法,gameconfig 是唯一 gameserv
test('uAgent_3:只读 app_<name>,settings 不参与身份或 gameserver', async () => {
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({
mode: 'release',
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'uAgent_3',
win: {
settings: new Proxy({}, { get() { throw new Error('settings forbidden'); } }),
@@ -84,7 +85,7 @@ test('uAgent_3:只读 app_<name>,settings 不参与身份或 gameserver', as
test('native-settings:不完整最终身份显式拒绝,不拿 build identity 兜底', async () => {
await assert.rejects(
resolveRuntimeConfig({
mode: 'release', hostKind: 'native-settings', win: {}, search: '', fetcher: returning(REMOTE),
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'native-settings', win: {}, search: '', fetcher: returning(REMOTE),
}),
/identity.*agentid.*channelid/i,
);
@@ -103,14 +104,14 @@ test('uAgent_3:所有允许 number 的身份字段都拒绝 NaN 和无穷值',
};
await assert.rejects(
resolveRuntimeConfig({
mode: 'release', hostKind: 'uAgent_3', win, search: '', fetcher: returning(REMOTE),
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'uAgent_3', win, search: '', fetcher: returning(REMOTE),
}),
new RegExp(`identity.*${sample.field}.*finite`, 'i'),
);
}
await assert.rejects(
resolveRuntimeConfig({
mode: 'release',
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'h5',
win: {},
search: '?agentid=A&channelid=C&version=NaN',
@@ -123,7 +124,7 @@ test('uAgent_3:所有允许 number 的身份字段都拒绝 NaN 和无穷值',
test('uAgent_3:拒绝宿主提供的非法身份字段类型', async () => {
await assert.rejects(
resolveRuntimeConfig({
mode: 'release',
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'uAgent_3',
win: { app_agent: true, app_channel: 'C', app_market: 4, app_gameconfig: '' },
search: '',
@@ -136,7 +137,7 @@ test('uAgent_3:拒绝宿主提供的非法身份字段类型', async () => {
test('显式 local debug:远程配置保留内存,连接服务器来自 profile', async () => {
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({
mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local', fetcher,
startupConfig: STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '?profile=local', fetcher,
});
assert.equal(result.source, 'remote');
assert.ok(result.gameserver);
@@ -149,7 +150,7 @@ test('显式 local debug:远程配置保留内存,连接服务器来自 prof
test('远程抓取失败显式报错,不降级 direct', async () => {
const fetcher: ConfigFetcher = { async fetch() { throw new Error('net down'); } };
await assert.rejects(
resolveRuntimeConfig({ mode: 'release', hostKind: 'h5', win: {}, search: '', fetcher }),
resolveRuntimeConfig({ startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'h5', win: {}, search: '', fetcher }),
/远程配置抓取失败/,
);
});
@@ -157,7 +158,7 @@ test('远程抓取失败显式报错,不降级 direct', async () => {
test('release ignores local profile but still fetches and selects remote servers', async () => {
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({ mode: 'release', hostKind: 'h5', win: {}, search: '?profile=local', fetcher });
const result = await resolveRuntimeConfig({ startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'h5', win: {}, search: '?profile=local', fetcher });
assert.equal(fetcher.calls.length, 1);
assert.deepEqual(result.servers, ['ws://5.5.5.5:5000', 'ws://5.5.5.5:5000']);
assert.equal(result.isDebugger, false);
@@ -168,14 +169,14 @@ test('JSON parse errors retain their classification through the runtime boundary
const { ConfigParseError } = await import('../../YouleNexus/assets/framework/config/remote-config.ts');
const error = new ConfigParseError('invalid JSON');
const fetcher = { async fetch() { throw error; } };
await assert.rejects(resolveRuntimeConfig({ mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local', fetcher }),
await assert.rejects(resolveRuntimeConfig({ startupConfig: STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '?profile=local', fetcher }),
actual => actual === error);
});
test('local consumes explicit loopback targets after valid remote JSON even when remote candidates are absent', async () => {
const raw = { visitor_server_tcp: 'unused.example:1234', notice: 'kept' };
const fetcher = returning(raw);
const result = await resolveRuntimeConfig({ mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local', fetcher });
const result = await resolveRuntimeConfig({ startupConfig: STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '?profile=local', fetcher });
assert.equal(fetcher.calls.length, 1);
assert.deepEqual(result.servers, ['ws://127.0.0.1:3088']);
assert.strictEqual(result.rawConfig, raw);
@@ -185,7 +186,33 @@ test('local consumes explicit loopback targets after valid remote JSON even when
test('remote profiles reject missing game/player addresses without selecting visitor', async () => {
for (const mode of ['debug', 'release'] as const) {
const fetcher = returning({ visitor_server_tcp: 'unused.example:1234' });
await assert.rejects(resolveRuntimeConfig({ mode, hostKind: 'h5', win: {}, search: '', fetcher }), /game_server_tcp\/player_server_tcp/);
await assert.rejects(resolveRuntimeConfig({ startupConfig: {...STARTUP_CONFIG, mode, useLocalServer: false}, hostKind: 'h5', win: {}, search: '', fetcher }), /game_server_tcp\/player_server_tcp/);
assert.equal(fetcher.calls.length, 1);
}
});
for (const mode of ['debug','release'] as const) for (const useLocalServer of [true,false]) test('independent source '+mode+'/'+useLocalServer+' ignores URL selectors', async () => {
const fetcher = returning(useLocalServer ? {visitor_server_tcp:'visitor:1'} : REMOTE);
const source = {...STARTUP_CONFIG,mode,useLocalServer};
const result = await resolveRuntimeConfig({startupConfig:source,hostKind:'h5',win:{},search:'?mode=debug&mode=release&profile=prod&profile=unknown',fetcher});
assert.equal(fetcher.calls.length,1); assert.equal(result.mode,mode); assert.equal(result.isDebugger,mode==='debug');
assert.deepEqual(result.servers,useLocalServer ? STARTUP_CONFIG.localServers : ['ws://5.5.5.5:5000','ws://5.5.5.5:5000']);
assert.deepEqual(result.startupConfig,source); assert.ok(Object.isFrozen(result.startupConfig));
source.localServers=['ws://localhost:9999']; assert.deepEqual(result.startupConfig.localServers,STARTUP_CONFIG.localServers);
});
test('default runtime starts without query configuration', async () => {
const result=await resolveRuntimeConfig({hostKind:'h5',win:{},search:'',fetcher:returning({notice:'kept'})});
assert.deepEqual(result.startupConfig,STARTUP_CONFIG); assert.deepEqual(result.servers,STARTUP_CONFIG.localServers);
});
test('invalid source fails before remote fetch, including null and missing fields', async () => {
for (const patch of [null, {useLocalServer: undefined}, {gameserver: ''}, {localServers: ['ws://elsewhere:3088']}]) {
const fetcher=returning(REMOTE);
await assert.rejects(resolveRuntimeConfig({startupConfig: patch === null ? null as any : {...STARTUP_CONFIG,...patch} as any,hostKind:'h5',win:{},search:'',fetcher}),/startup config/i);
assert.equal(fetcher.calls.length,0);
}
});
for (const mode of ['debug','release'] as const) for (const useLocalServer of [true,false]) test('remote failure never falls back '+mode+'/'+useLocalServer, async () => {
const fetcher={async fetch(){throw new Error('remote unavailable');}};
await assert.rejects(resolveRuntimeConfig({startupConfig:{...STARTUP_CONFIG,mode,useLocalServer},hostKind:'h5',win:{},search:'',fetcher}),/远程配置抓取失败/);
});
@@ -1,16 +1,7 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { resolveRuntimeMode } from '../../YouleNexus/assets/framework/config/runtime-mode.ts';
test('override 优先于构建类型', () => {
assert.equal(resolveRuntimeMode(true, 'release'), 'release');
assert.equal(resolveRuntimeMode(false, 'debug'), 'debug');
});
test('无 override:debug 构建 → debug', () => {
assert.equal(resolveRuntimeMode(true, null), 'debug');
});
test('无 override:release 构建 → release', () => {
assert.equal(resolveRuntimeMode(false, null), 'release');
test('runtime mode is an explicit pure reader without environment defaults', () => {
assert.equal(resolveRuntimeMode('debug'),'debug'); assert.equal(resolveRuntimeMode('release'),'release');
for (const value of [undefined,null,true,'other']) assert.throws(() => resolveRuntimeMode(value as any), /mode/i);
});
@@ -13,7 +13,7 @@ class MapStorage implements LocalStoragePort {
}
function resolve(search = '?profile=local') {
return resolveLocalStartup({ mode: 'debug', hostKind: 'h5', win: {}, search,
return resolveLocalStartup({ hostKind: 'h5', win: {}, search,
fetcher: { async fetch() { return { player_server_tcp: 'remote.example:3088' }; } } });
}
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { remoteFixture } from '../fixtures/remote-config.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
@@ -12,7 +13,7 @@ import type { GameHost } from '../../YouleNexus/assets/framework/sdk/index.ts';
import { FakeTransport } from '../helpers/fake-transport.ts';
const CONFIG: RuntimeConfig = {
mode: 'debug', isDebugger: true,
startupConfig: STARTUP_CONFIG, mode: 'debug', isDebugger: true,
identity: { agentid: 'A', channelid: 'C', gameid: '41', version: 1, marketid: 4 },
source: 'remote', servers: ['ws://agent'], gameserver: 'https://fixture/config', ...remoteFixture(),
};
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { test } from 'node:test';
@@ -121,7 +122,7 @@ test('real modern vertical replay preserves wire contracts, room ordering and te
const runtime = new PlatformRuntime({
gameEntry: entry,
resolveRuntimeConfig: () => resolveRuntimeConfig({
mode: 'release', hostKind: 'native-settings', search: '',
startupConfig: {...STARTUP_CONFIG,mode:'release',useLocalServer:false}, hostKind: 'native-settings', search: '',
win: { settings: {
getothername(name: string) {
nativeCalls.push(`getothername:${name}`);
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { test } from 'node:test';
@@ -39,7 +40,7 @@ async function setup(overrides: { config?: Partial<ResolveRuntimeConfigOptions>;
const values = new Map<string, string>(), frames = new Set<() => void>();
let fetches = 0;
const host = api.createLocalLoginHost({
configOptions: { mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local&version=731',
configOptions: { hostKind: 'h5', win: {}, search: '?profile=local&version=731',
fetcher: { async fetch() { fetches++; return { player_server_tcp: 'remote.example:3088' }; } }, ...overrides.config },
storage: { getItem: key => values.get(key) ?? null, setItem: (key, value) => { values.set(key, value); } },
random: () => .5, now: () => 123456, sampleHost: () => ({ location: null }),
@@ -138,7 +139,7 @@ for (const phase of ['config', 'resources', 'prepare'] as const) test(`stop duri
assert.deepEqual(h.failures, []);
});
for (const config of [{ search: '?profile=prod' }, { hostKind: 'native-settings' as const }]) test('invalid local input cannot create an account or connection', async () => {
for (const config of [{ startupConfig: {...STARTUP_CONFIG,mode:'release' as const} }, { startupConfig: {...STARTUP_CONFIG,useLocalServer:false} }, { hostKind: 'native-settings' as const }]) test('invalid local input cannot create an account or connection', async () => {
const h = await setup({ config });
await assert.rejects(h.host.start(), /local startup/);
assert.equal(h.values.size, 0); assert.equal(h.sockets.length, 0); assert.equal(h.failures.length, 1);
@@ -242,3 +243,10 @@ test('startup diagnostics exclude raw remote fields', async t => {
t.after(() => h.host.stop()); await h.ready();
assert.equal(JSON.stringify(h.evidence).includes('DO_NOT_RECORD_REMOTE_SECRET'), false);
});
test('default no-query host reaches login ready and ignores conflicting selectors', async t => {
for (const search of ['', '?mode=release&mode=other&profile=prod&profile=unknown']) {
const h=await setup({config:{search}}); t.after(()=>h.host.stop()); await h.ready();
assert.equal(h.fetches(),1); assert.equal(h.host.getSnapshot().ready,true); assert.ok(h.activations.includes('login')); assert.equal(h.failures.length,0);
}
});
@@ -36,7 +36,7 @@ async function setup(overrides: {
const module = await import('../../YouleNexus/assets/scripts/local-platform-login/local-login-transport.ts')
.catch((error: unknown) => assert.fail(`LocalLoginTransport implementation must exist: ${String(error)}`));
const context = await resolveLocalStartup({
mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local',
hostKind: 'h5', win: {}, search: '?profile=local',
fetcher: { async fetch() { return { player_server_tcp: 'remote.example:3088' }; } },
});
const sockets: FakeSocket[] = [];
@@ -14,7 +14,7 @@ function fixture(name: string) {
async function setup(recordHook?: (event: { kind: string; data: unknown }) => void) {
const api = await import('../../YouleNexus/assets/scripts/local-platform-login/local-login-wire.ts')
.catch(error => assert.fail(`Local login Wire implementation must exist: ${String(error)}`));
const context = await resolveLocalStartup({ mode: 'debug', hostKind: 'h5', win: {}, search: '?profile=local',
const context = await resolveLocalStartup({ hostKind: 'h5', win: {}, search: '?profile=local',
fetcher: { async fetch() { return { player_server_tcp: 'remote.example:3088' }; } } });
const transports: FakeTransport[] = [];
const events: WireEvent[] = [];
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { remoteFixture } from '../fixtures/remote-config.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
@@ -11,7 +12,7 @@ import {
} from '../../YouleNexus/assets/framework/protocol/contracts/index.ts';
const RUNTIME_CONFIG: RuntimeConfig = Object.freeze({
mode: 'release',
startupConfig: {...STARTUP_CONFIG,mode:'release' as const,useLocalServer:false}, mode: 'release',
isDebugger: false,
identity: Object.freeze({
agentid: 'agent-from-config',
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { remoteFixture } from '../fixtures/remote-config.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
@@ -30,7 +31,7 @@ import {
} from '../../YouleNexus/assets/framework/sdk/runtime/game-session-host.ts';
const RUNTIME_CONFIG: RuntimeConfig = Object.freeze({
mode: 'release',
startupConfig: {...STARTUP_CONFIG,mode:'release' as const,useLocalServer:false}, mode: 'release',
isDebugger: false,
identity: Object.freeze({
agentid: 17,
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { remoteFixture } from '../fixtures/remote-config.ts';
import { readFileSync } from 'node:fs';
import assert from 'node:assert/strict';
@@ -48,7 +49,7 @@ const DEVICE = Object.freeze({
function runtimeConfig(gameid: string | number = '41'): RuntimeConfig {
return Object.freeze({
mode: 'debug',
startupConfig: STARTUP_CONFIG, mode: 'debug',
isDebugger: true,
identity: Object.freeze({
agentid: 'veRa0qrBf',
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { remoteFixture } from '../fixtures/remote-config.ts';
import { test } from 'node:test';
import assert from 'node:assert/strict';
@@ -11,11 +12,11 @@ const IDENTITY = { agentid: 'A', channelid: 'C', gameid: 'G', marketid: 4, versi
function bootstrapOf(): BootstrapResult {
return {
mode: 'debug', isDebugger: true,
startupConfig: STARTUP_CONFIG, mode: 'debug', isDebugger: true,
identity: IDENTITY,
servers: ['ws://test'],
...remoteFixture(),
profile: { name: 'debug' },
profile: { name: 'local' },
};
}
@@ -1,3 +1,4 @@
import { STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
import { remoteFixture } from '../fixtures/remote-config.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
@@ -75,7 +76,7 @@ test('assertGameContract accepts a fixed-four game with its real nested roomtype
});
const RUNTIME_CONFIG: RuntimeConfig = Object.freeze({
mode: 'release',
startupConfig: {...STARTUP_CONFIG,mode:'release' as const,useLocalServer:false}, mode: 'release',
isDebugger: false,
identity: Object.freeze({
agentid: 'fixture-agent', channelid: 'fixture-channel', gameid: '41',