Files
youle_cocos/cocoscreator_projects/framework-tests/config/runtime-config.test.ts
T

256 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { STARTUP_CONFIG as EDITABLE_STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
// 固定测试来源,允许开发者独立切换项目的 local/remote 开关。
const STARTUP_CONFIG = Object.freeze({...EDITABLE_STARTUP_CONFIG, configSource:'remote' as const});
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
import { resolveRuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
import type { ConfigFetcher } from '../../YouleNexus/assets/framework/config/remote-config.ts';
function returning(value: unknown): ConfigFetcher & { calls: string[] } {
const calls: string[] = [];
return {
calls,
async fetch(gameserver: string) { calls.push(gameserver); return value; },
};
}
const REMOTE = { player_server_tcp: ['5.5.5.5:5000', '5.5.5.5:5000'] };
test('h5:只读 query 身份,不访问原生宿主,并生成严格 remote 配置', async () => {
const fetcher = returning(REMOTE);
const throwingSettings = new Proxy({}, { get() { throw new Error('native access forbidden'); } });
const result = await resolveRuntimeConfig({
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'h5',
win: { settings: throwingSettings },
search: '?agentid=HA&channelid=HC',
fetcher,
cacheBust: () => '123',
});
assert.deepEqual(result.identity, { ...BUILD_IDENTITY, agentid: 'HA', channelid: 'HC' });
assert.deepEqual(result.servers, ['ws://5.5.5.5:5000', 'ws://5.5.5.5:5000']);
assert.equal(result.source, 'remote');
assert.equal(result.gameserver, STARTUP_CONFIG.gameserver);
assert.equal(result.rawConfig, REMOTE);
assert.deepEqual(fetcher.calls, [STARTUP_CONFIG.gameserver]);
assert.equal(Object.isFrozen(result), true);
assert.equal(Object.isFrozen(result.identity), true);
assert.equal(Object.isFrozen(result.servers), true);
});
test('native-settings:使用四个原生方法,gameconfig 是唯一 gameserver 注入名', async () => {
const calls: string[] = [];
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'native-settings',
win: {
settings: {
getothername(name: string) { calls.push(`getothername:${name}`); return name === 'agent' ? 'NA' : 'host-path#80-a'; },
getchannelName() { calls.push('getchannelName'); return 'NC'; },
getmarketname() { calls.push('getmarketname'); return 7; },
},
},
search: '',
fetcher,
});
assert.deepEqual(calls, ['getothername:agent', 'getchannelName', 'getmarketname', 'getothername:gameconfig']);
assert.deepEqual(result.identity, {
agentid: 'NA', channelid: 'NC', gameid: BUILD_IDENTITY.gameid, marketid: 7, version: BUILD_IDENTITY.version,
});
assert.equal(result.gameserver, 'http://host/path:80/a.txt');
assert.deepEqual(fetcher.calls, ['http://host/path:80/a.txt']);
});
test('uAgent_3:只读 app_<name>,settings 不参与身份或 gameserver', async () => {
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'uAgent_3',
win: {
settings: new Proxy({}, { get() { throw new Error('settings forbidden'); } }),
app_agent: 'UA', app_channel: 'UC', app_market: 9, app_gameconfig: 'u-host#80-cfg',
},
search: '',
fetcher,
});
assert.deepEqual(result.identity, {
agentid: 'UA', channelid: 'UC', gameid: BUILD_IDENTITY.gameid, marketid: 9, version: BUILD_IDENTITY.version,
});
assert.equal(result.gameserver, 'http://u/host:80/cfg.txt');
});
test('native-settings:不完整最终身份显式拒绝,不拿 build identity 兜底', async () => {
await assert.rejects(
resolveRuntimeConfig({
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'native-settings', win: {}, search: '', fetcher: returning(REMOTE),
}),
/identity.*agentid.*channelid/i,
);
});
test('uAgent_3:所有允许 number 的身份字段都拒绝 NaN 和无穷值', async () => {
const cases = [
{ key: 'app_agent', value: Number.NaN, field: 'agentid' },
{ key: 'app_channel', value: Number.POSITIVE_INFINITY, field: 'channelid' },
{ key: 'app_market', value: Number.NEGATIVE_INFINITY, field: 'marketid' },
] as const;
for (const sample of cases) {
const win = {
app_agent: 'A', app_channel: 'C', app_market: 4, app_gameconfig: '',
[sample.key]: sample.value,
};
await assert.rejects(
resolveRuntimeConfig({
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({
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'h5',
win: {},
search: '?agentid=A&channelid=C&version=NaN',
fetcher: returning(REMOTE),
}),
/identity.*version.*finite/i,
);
});
test('uAgent_3:拒绝宿主提供的非法身份字段类型', async () => {
await assert.rejects(
resolveRuntimeConfig({
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
hostKind: 'uAgent_3',
win: { app_agent: true, app_channel: 'C', app_market: 4, app_gameconfig: '' },
search: '',
fetcher: returning(REMOTE),
}),
/identity.*agentid.*type/i,
);
});
test('显式 local debug:远程配置保留内存,连接服务器来自 profile', async () => {
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({
startupConfig: STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '?profile=local', fetcher,
});
assert.equal(result.source, 'remote');
assert.ok(result.gameserver);
assert.equal(result.rawConfig, REMOTE);
assert.strictEqual(result.remoteConfig.raw, result.rawConfig);
assert.deepEqual(result.servers, ['ws://127.0.0.1:3088']);
assert.equal(fetcher.calls.length, 1);
});
test('远程抓取失败显式报错,不降级 direct', async () => {
const fetcher: ConfigFetcher = { async fetch() { throw new Error('net down'); } };
await assert.rejects(
resolveRuntimeConfig({ startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'h5', win: {}, search: '', fetcher }),
/远程配置抓取失败/,
);
});
test('release ignores local profile but still fetches and selects remote servers', async () => {
const fetcher = returning(REMOTE);
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);
});
test('JSON parse errors retain their classification through the runtime boundary', async () => {
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({ 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({ 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);
assert.equal(result.remoteConfig.getValue('notice'), 'kept');
});
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({ 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({startupConfig:STARTUP_CONFIG,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}),/远程配置抓取失败/);
});
// 本地来源必须在运行时边界隔离网络读取,并复用正式配置的层级取值规则。
test('local txt selects one source, ignores gameconfig and preserves scoped values', async () => {
const remote = returning(REMOTE);
const local = returning({showVisitor: 1, agentlist: [{agentid: BUILD_IDENTITY.agentid, deviceLogin: true}]});
const result = await resolveRuntimeConfig({startupConfig:{...STARTUP_CONFIG, configSource:'local'},
hostKind:'h5',win:{},search:'?gameconfig=unwanted-host',fetcher:remote,localFetcher:local});
assert.equal(result.source,'local');
assert.deepEqual(remote.calls,[]);
assert.deepEqual(local.calls,[STARTUP_CONFIG.localConfigResource]);
assert.equal(result.remoteConfig.getValue('showVisitor'),1);
assert.equal(result.remoteConfig.getValue('deviceLogin'),true);
assert.deepEqual(result.servers,STARTUP_CONFIG.localServers);
});
test('local txt errors never fall back to remote, release rejects before I/O', async () => {
const remote=returning(REMOTE); let localCalls=0;
const localFetcher={async fetch(){localCalls++;throw Error('missing txt');}};
const options={startupConfig:{...STARTUP_CONFIG,configSource:'local' as const},hostKind:'h5' as const,win:{},search:'',fetcher:remote,localFetcher};
await assert.rejects(resolveRuntimeConfig(options),/本地配置读取失败/);
assert.equal(localCalls,1);assert.equal(remote.calls.length,0);
await assert.rejects(resolveRuntimeConfig({...options,startupConfig:{...options.startupConfig,mode:'release'}}),/configSource/);
assert.equal(localCalls,1);
await assert.rejects(resolveRuntimeConfig({...options,localFetcher:undefined}),/localFetcher/);
});
test('local txt and socket target are independent; remote mode never touches local loader', async () => {
const remote=returning(REMOTE),local=returning(REMOTE);
const options={hostKind:'h5' as const,win:{},search:'',fetcher:remote,localFetcher:local};
const result=await resolveRuntimeConfig({...options,startupConfig:{...STARTUP_CONFIG,configSource:'local',useLocalServer:false}});
assert.deepEqual(result.servers,['ws://5.5.5.5:5000','ws://5.5.5.5:5000']);
assert.equal(remote.calls.length,0);assert.equal(local.calls.length,1);
await resolveRuntimeConfig({...options,startupConfig:{...STARTUP_CONFIG,configSource:'remote'}});
assert.equal(remote.calls.length,1);assert.equal(local.calls.length,1);
});