feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { BUILD_IDENTITY } from '../fixtures/build-identity.ts';
|
||||
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});
|
||||
@@ -10,7 +11,7 @@ test('resolveBootstrap:显式 hostKind 透传严格 RuntimeConfig,并保留
|
||||
const fetcher: ConfigFetcher = {
|
||||
async fetch() { return { player_server_tcp: '5.5.5.5:5000' }; },
|
||||
};
|
||||
const result = await resolveBootstrap({
|
||||
const result = await resolveBootstrap({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig:STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '', fetcher,
|
||||
});
|
||||
assert.equal(result.source, 'remote');
|
||||
@@ -21,7 +22,7 @@ test('resolveBootstrap:显式 hostKind 透传严格 RuntimeConfig,并保留
|
||||
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'};}}});
|
||||
const result=await resolveBootstrap({buildIdentity:BUILD_IDENTITY,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']);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {getGameDefinition} from '../fixtures/game-definitions.ts';
|
||||
import {createBuildIdentity,WEB_MARKET_ID} from '../../YouleNexus/assets/app/composition/build-identity.ts';
|
||||
import {parseVersionManifest} from '../../YouleNexus/assets/app/composition/version-manifest.js';
|
||||
import {resolveRuntimeConfig} from '../../YouleNexus/assets/framework/config/runtime-config.ts';
|
||||
import {STARTUP_CONFIG} from '../../YouleNexus/assets/framework/config/profiles.ts';
|
||||
function manifest(key:string,dir:string){
|
||||
const definition=getGameDefinition(key);
|
||||
assert.equal(definition.config.versionResource,key+'/version');
|
||||
return parseVersionManifest(readFileSync(new URL('../../YouleNexus/assets/games/'+dir+'/resources/'+definition.config.versionResource+'.xml',import.meta.url),'utf8'));
|
||||
}
|
||||
test('selected game reads identity and both versions from its XML',()=>{
|
||||
const manifests=[manifest('erqiwang','erqiwang'),manifest('Game_Surface_3','template')];
|
||||
for(const source of manifests){const identity=createBuildIdentity(source);
|
||||
assert.equal(identity.gameid,source.gameid);assert.equal(identity.agentid,source.agentid);
|
||||
assert.equal(identity.channelid,source.channelid);assert.equal(identity.version,source.versionCode);
|
||||
assert.equal(identity.marketid,WEB_MARKET_ID);assert.ok(Object.isFrozen(identity));
|
||||
}
|
||||
assert.notEqual(manifests[0].gameid,manifests[1].gameid);
|
||||
assert.throws(()=>getGameDefinition('missing'),/Unknown game/);
|
||||
assert.throws(()=>createBuildIdentity({...manifests[0],gameid:''}),/gameid/);
|
||||
});
|
||||
test('XML edits drive Web identity while native channel remains native-owned',async()=>{
|
||||
const source={...manifest('erqiwang','erqiwang'),gameid:'xml-game',agentid:'xml-agent',channelid:'xml-channel',versionCode:37,version:'3.7'};
|
||||
const options={buildIdentity:createBuildIdentity(source),startupConfig:{...STARTUP_CONFIG,configSource:'remote' as const},
|
||||
search:'',fetcher:{async fetch(){return {player_server_tcp:'remote.example:3088'};}}};
|
||||
const web=await resolveRuntimeConfig({...options,hostKind:'h5',win:{}});
|
||||
assert.deepEqual(web.identity,{agentid:'xml-agent',channelid:'xml-channel',gameid:'xml-game',version:37,marketid:WEB_MARKET_ID});
|
||||
const native=await resolveRuntimeConfig({...options,hostKind:'uAgent_3',win:{app_agent:'native-a',app_channel:'native-c',app_market:9} as any});
|
||||
assert.deepEqual(native.identity,{agentid:'native-a',channelid:'native-c',marketid:9,gameid:'xml-game',version:37});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { resolveIdentity } from '../../YouleNexus/assets/framework/config/identity.ts';
|
||||
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
|
||||
import { BUILD_IDENTITY } from '../fixtures/build-identity.ts';
|
||||
|
||||
test('BUILD_IDENTITY 含全部字段且 gameid 为固定 token', () => {
|
||||
assert.ok(BUILD_IDENTITY.gameid.length > 0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as config from '../../YouleNexus/assets/framework/config/remote-config.ts';
|
||||
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
|
||||
import { BUILD_IDENTITY } from '../fixtures/build-identity.ts';
|
||||
|
||||
test('launchWaitTime follows legacy default, hall own field and truthy hierarchy precedence', () => {
|
||||
const resolve = (config as any).resolveLaunchWaitTime;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { STARTUP_CONFIG as EDITABLE_STARTUP_CONFIG } from '../../YouleNexus/asse
|
||||
const STARTUP_CONFIG = Object.freeze({...EDITABLE_STARTUP_CONFIG, configSource:'remote' as const});
|
||||
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';
|
||||
import { BUILD_IDENTITY } from '../fixtures/build-identity.ts';
|
||||
|
||||
function options(search = ''): ResolveRuntimeConfigOptions & {
|
||||
fetcher: ConfigFetcher & { calls: number };
|
||||
@@ -24,7 +24,7 @@ function options(search = ''): ResolveRuntimeConfigOptions & {
|
||||
return { player_server_tcp: 'remote.example:3088' };
|
||||
},
|
||||
};
|
||||
return { startupConfig:STARTUP_CONFIG, hostKind: 'h5', win: {}, search, fetcher };
|
||||
return { buildIdentity:BUILD_IDENTITY, startupConfig:STARTUP_CONFIG, hostKind: 'h5', win: {}, search, fetcher };
|
||||
}
|
||||
|
||||
test('local debug fetches remote config before choosing authoritative local servers', async () => {
|
||||
@@ -179,7 +179,7 @@ test('local consumers validate the injected source snapshot and its exact target
|
||||
|
||||
test('local startup validates the selected project txt source', async () => {
|
||||
const localFetcher={async fetch(){return {showVisitor:1};}};
|
||||
const context=await resolveLocalStartup({startupConfig:{...STARTUP_CONFIG,configSource:'local'},hostKind:'h5',win:{},search:'',
|
||||
const context=await resolveLocalStartup({buildIdentity:BUILD_IDENTITY,startupConfig:{...STARTUP_CONFIG,configSource:'local'},hostKind:'h5',win:{},search:'',
|
||||
fetcher:{async fetch(){throw Error('remote forbidden');}},localFetcher});
|
||||
assert.equal(context.config.source,'local');
|
||||
assert.equal(context.config.gameserver,STARTUP_CONFIG.localConfigResource);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BUILD_IDENTITY } from '../fixtures/build-identity.ts';
|
||||
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});
|
||||
@@ -28,7 +29,7 @@ test('resolveRuntimeConfig:把 cacheBust 注入唯一的 HTTP 请求构造点'
|
||||
return new Response('{"player_server_tcp":"10.0.0.1:3088"}');
|
||||
});
|
||||
|
||||
await resolveRuntimeConfig({
|
||||
await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG,mode:'release',useLocalServer:false},
|
||||
hostKind: 'h5',
|
||||
win: {},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { STARTUP_CONFIG as EDITABLE_STARTUP_CONFIG } from '../../YouleNexus/asse
|
||||
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 { BUILD_IDENTITY } from '../fixtures/build-identity.ts';
|
||||
import { resolveRuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
|
||||
import type { ConfigFetcher } from '../../YouleNexus/assets/framework/config/remote-config.ts';
|
||||
|
||||
@@ -20,7 +20,7 @@ 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({
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
|
||||
hostKind: 'h5',
|
||||
win: { settings: throwingSettings },
|
||||
@@ -43,7 +43,7 @@ test('h5:只读 query 身份,不访问原生宿主,并生成严格 remote
|
||||
test('native-settings:使用四个原生方法,gameconfig 是唯一 gameserver 注入名', async () => {
|
||||
const calls: string[] = [];
|
||||
const fetcher = returning(REMOTE);
|
||||
const result = await resolveRuntimeConfig({
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
|
||||
hostKind: 'native-settings',
|
||||
win: {
|
||||
@@ -67,7 +67,7 @@ test('native-settings:使用四个原生方法,gameconfig 是唯一 gameserv
|
||||
|
||||
test('uAgent_3:只读 app_<name>,settings 不参与身份或 gameserver', async () => {
|
||||
const fetcher = returning(REMOTE);
|
||||
const result = await resolveRuntimeConfig({
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
|
||||
hostKind: 'uAgent_3',
|
||||
win: {
|
||||
@@ -86,7 +86,7 @@ test('uAgent_3:只读 app_<name>,settings 不参与身份或 gameserver', as
|
||||
|
||||
test('native-settings:不完整最终身份显式拒绝,不拿 build identity 兜底', async () => {
|
||||
await assert.rejects(
|
||||
resolveRuntimeConfig({
|
||||
resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'native-settings', win: {}, search: '', fetcher: returning(REMOTE),
|
||||
}),
|
||||
/identity.*agentid.*channelid/i,
|
||||
@@ -105,14 +105,14 @@ test('uAgent_3:所有允许 number 的身份字段都拒绝 NaN 和无穷值',
|
||||
[sample.key]: sample.value,
|
||||
};
|
||||
await assert.rejects(
|
||||
resolveRuntimeConfig({
|
||||
resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
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({
|
||||
resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
|
||||
hostKind: 'h5',
|
||||
win: {},
|
||||
@@ -125,7 +125,7 @@ test('uAgent_3:所有允许 number 的身份字段都拒绝 NaN 和无穷值',
|
||||
|
||||
test('uAgent_3:拒绝宿主提供的非法身份字段类型', async () => {
|
||||
await assert.rejects(
|
||||
resolveRuntimeConfig({
|
||||
resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false},
|
||||
hostKind: 'uAgent_3',
|
||||
win: { app_agent: true, app_channel: 'C', app_market: 4, app_gameconfig: '' },
|
||||
@@ -138,7 +138,7 @@ test('uAgent_3:拒绝宿主提供的非法身份字段类型', async () => {
|
||||
|
||||
test('显式 local debug:远程配置保留内存,连接服务器来自 profile', async () => {
|
||||
const fetcher = returning(REMOTE);
|
||||
const result = await resolveRuntimeConfig({
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,
|
||||
startupConfig: STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '?profile=local', fetcher,
|
||||
});
|
||||
assert.equal(result.source, 'remote');
|
||||
@@ -152,7 +152,7 @@ test('显式 local debug:远程配置保留内存,连接服务器来自 prof
|
||||
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 }),
|
||||
resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY, startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'h5', win: {}, search: '', fetcher }),
|
||||
/远程配置抓取失败/,
|
||||
);
|
||||
});
|
||||
@@ -160,7 +160,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({ startupConfig: {...STARTUP_CONFIG, mode: 'release', useLocalServer: false}, hostKind: 'h5', win: {}, search: '?profile=local', fetcher });
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY, 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);
|
||||
@@ -171,14 +171,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({ startupConfig: STARTUP_CONFIG, hostKind: 'h5', win: {}, search: '?profile=local', fetcher }),
|
||||
await assert.rejects(resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY, 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 });
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY, 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);
|
||||
@@ -188,7 +188,7 @@ 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({ startupConfig: {...STARTUP_CONFIG, mode, useLocalServer: false}, hostKind: 'h5', win: {}, search: '', fetcher }), /game_server_tcp\/player_server_tcp/);
|
||||
await assert.rejects(resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY, startupConfig: {...STARTUP_CONFIG, mode, useLocalServer: false}, hostKind: 'h5', win: {}, search: '', fetcher }), /game_server_tcp\/player_server_tcp/);
|
||||
assert.equal(fetcher.calls.length, 1);
|
||||
}
|
||||
});
|
||||
@@ -196,34 +196,34 @@ test('remote profiles reject missing game/player addresses without selecting vis
|
||||
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});
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,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'})});
|
||||
const result=await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,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);
|
||||
await assert.rejects(resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,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}),/远程配置抓取失败/);
|
||||
await assert.rejects(resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,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'},
|
||||
const result = await resolveRuntimeConfig({buildIdentity:BUILD_IDENTITY,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,[]);
|
||||
@@ -236,7 +236,7 @@ test('local txt selects one source, ignores gameconfig and preserves scoped valu
|
||||
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};
|
||||
const options={buildIdentity:BUILD_IDENTITY,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/);
|
||||
@@ -246,10 +246,13 @@ test('local txt errors never fall back to remote, release rejects before I/O', a
|
||||
|
||||
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 options={buildIdentity:BUILD_IDENTITY,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);
|
||||
});
|
||||
|
||||
test('application injects selected game identity',async()=>{for(const gameid of ['one','two']){const result=await resolveRuntimeConfig({startupConfig:STARTUP_CONFIG,buildIdentity:{...BUILD_IDENTITY,gameid},hostKind:'h5',win:{},search:'?gameid=other',fetcher:returning(REMOTE)});assert.equal(result.identity.gameid,gameid);}});
|
||||
test('missing application identity fails before I/O',async()=>{const fetcher=returning(REMOTE);await assert.rejects(resolveRuntimeConfig({startupConfig:STARTUP_CONFIG,hostKind:'h5',win:{},search:'',fetcher} as any),/buildIdentity/);assert.equal(fetcher.calls.length,0);});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {createRequire} from 'node:module';
|
||||
const {parseVersionManifest}=createRequire(import.meta.url)('../../YouleNexus/assets/app/composition/version-manifest.js');
|
||||
const xml='<?xml version="1.0" encoding="UTF-8"?><game><agent id="a" name="代理"/><game id="g" name="游戏 & 测试"/><channel id="c" name="渠道"/><version value="8" name="1.8"/></game>';
|
||||
test('legacy XML maps numeric code separately from display version',()=>{
|
||||
const result=parseVersionManifest(xml);assert.equal(result.versionCode,8);assert.equal(result.version,'1.8');assert.equal(result.gameid,'g');assert.equal(result.gameName,'游戏 & 测试');assert.ok(Object.isFrozen(result));
|
||||
});
|
||||
test('bad XML and missing or ambiguous source fields fail explicitly',()=>{
|
||||
for(const bad of ['',xml.replace('value="8"','value="1.8"'),xml.replace('value="8"','value="9007199254740992"'),xml.replace('name="1.8"','name=""'),xml.replace('<game id="g"','<game id=""'),xml.replace('</game>','<version value="9" name="2"/></game>'),xml.replace('</game>','</wrong>'),'<!DOCTYPE game>'+xml,xml.replace('id="a"','id="a" id="b"')])assert.throws(()=>parseVersionManifest(bad),/version.xml/);
|
||||
});
|
||||
Reference in New Issue
Block a user