fix(config): resolve remote hierarchy in memory for every profile
This commit is contained in:
@@ -1,54 +1,68 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { runInNewContext } from 'node:vm';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseUrlServers } from '../../YouleNexus/assets/framework/config/remote-config.ts';
|
||||
|
||||
const single = JSON.parse(readFileSync(
|
||||
new URL('../fixtures/contracts/remote-config-single.json', import.meta.url),
|
||||
'utf8',
|
||||
)) as unknown;
|
||||
const array = JSON.parse(readFileSync(
|
||||
new URL('../fixtures/contracts/remote-config-array.json', import.meta.url),
|
||||
'utf8',
|
||||
)) as unknown;
|
||||
|
||||
test('parseUrlServers:读取 data.urlserver 字符串并补 ws://', () => {
|
||||
assert.deepEqual(parseUrlServers(single), ['ws://10.0.0.1:3088']);
|
||||
import * as api from '../../YouleNexus/assets/framework/config/remote-config.ts';
|
||||
const identity = { agentid: '11', gameid: '22', channelid: '33', marketid: 44, version: 1 };
|
||||
const source = readFileSync(new URL('../../../projects/Game_Surface_3/js/00_Surface/12_Logic.js', import.meta.url), 'utf8');
|
||||
const start = source.indexOf('function get_paravalue(o_config, paraname){');
|
||||
const legacySource = source.slice(start, source.indexOf('function getConfig_Succ', start));
|
||||
function view(raw: unknown, selected = identity) {
|
||||
assert.equal(typeof api.createRemoteConfig, 'function', 'remote memory view must be exported');
|
||||
return api.createRemoteConfig(raw, selected);
|
||||
}
|
||||
function legacy(raw: unknown, name: string, selected = identity) {
|
||||
return runInNewContext(`${legacySource}; get_paravalue(wrapper, name)`, {
|
||||
wrapper: { data: raw }, name,
|
||||
GameData: { AgentId: selected.agentid, GameId: selected.gameid, ChannelId: selected.channelid, marketID: selected.marketid },
|
||||
});
|
||||
}
|
||||
test('memory view agrees with actual legacy function across all five scopes and preserves raw objects', () => {
|
||||
const scopes: Record<string, unknown>[] = [
|
||||
{ hall_config: { root: true }, rootOnly: 'root', falsy: 'inherited' },
|
||||
{ agentid: 11, hall_config: { agent: true }, agentOnly: 'agent', falsy: 0 },
|
||||
{ gameid: 22, hall_config: { game: true }, gameOnly: 'game', falsy: false },
|
||||
{ channelid: 33, hall_config: { channel: true }, channelOnly: 'channel', falsy: '' },
|
||||
{ marketid: '44', hall_config: ['market'], marketOnly: 'market', falsy: null },
|
||||
];
|
||||
const lists = ['agentlist', 'gamelist', 'channellist', 'marketlist'];
|
||||
for (let depth = 0; depth < scopes.length; depth++) {
|
||||
if (depth > 0) scopes[depth - 1][lists[depth - 1]] = [scopes[depth], { ...scopes[depth], hall_config: 'duplicate must lose' }];
|
||||
const result = view(scopes[0]);
|
||||
assert.strictEqual(result.raw, scopes[0]);
|
||||
for (const name of ['hall_config', 'rootOnly', 'agentOnly', 'gameOnly', 'channelOnly', 'marketOnly', 'falsy', 'missing']) {
|
||||
assert.deepEqual(result.getValue(name), legacy(scopes[0], name));
|
||||
}
|
||||
assert.strictEqual(result.getValue('hall_config'), scopes[depth].hall_config);
|
||||
assert.equal(result.getValue('falsy'), 'inherited');
|
||||
}
|
||||
assert.equal(view(scopes[0], { ...identity, marketid: 0 }).getValue('marketOnly'), null);
|
||||
assert.deepEqual(view(scopes[0], { ...identity, agentid: 'missing' }).getValue('hall_config'), { root: true });
|
||||
});
|
||||
|
||||
test('parseUrlServers:读取 data.urlserver 数组并保留已有 wss://', () => {
|
||||
assert.deepEqual(parseUrlServers(array), ['ws://10.0.0.1:3088', 'wss://backup.example/ws']);
|
||||
test('falsy root values remain null and missing identity levels stop inheritance', () => {
|
||||
for (const value of [0, false, '', null]) assert.equal(view({ x: value }).getValue('x'), null);
|
||||
assert.equal(view({ x: 'root', agentlist: [{ agentid: 'other', x: 'wrong' }] }).getValue('x'), 'root');
|
||||
});
|
||||
|
||||
test('parseUrlServers:拒绝旧的 *_server_tcp 字段', () => {
|
||||
assert.throws(
|
||||
() => parseUrlServers({ data: { player_server_tcp: 'wrong:1' } }),
|
||||
/data\.urlserver/,
|
||||
);
|
||||
test('invalid root and present hierarchy fail explicitly', () => {
|
||||
for (const raw of [null, [], 'json', { agentlist: {} }, { agentlist: [null] },
|
||||
{ agentlist: [{ agentid: 11, gamelist: {} }] }, { agentlist: [{ agentid: 11, gamelist: [{ gameid: 22, channellist: [false] }] }] }]) {
|
||||
assert.throws(() => view(raw), api.ConfigParseError);
|
||||
}
|
||||
});
|
||||
|
||||
test('parseUrlServers:保留原顺序和重复项,并冻结结果', () => {
|
||||
const servers = parseUrlServers({ data: { urlserver: ['same:1', 'same:1', 'ws://last:2'] } });
|
||||
assert.deepEqual(servers, ['ws://same:1', 'ws://same:1', 'ws://last:2']);
|
||||
assert.equal(Object.isFrozen(servers), true);
|
||||
test('server selection prefers scoped game then player and preserves order and duplicates', () => {
|
||||
for (const [raw, expected] of [
|
||||
[{ player_server_tcp: 'remote.example:3088' }, ['ws://remote.example:3088']],
|
||||
[{ player_server_tcp: 'unused', game_server_tcp: ['same:1', 'same:1', 'wss://last/ws'] }, ['ws://same:1', 'ws://same:1', 'wss://last/ws']],
|
||||
[{ game_server_tcp: '', player_server_tcp: 'player:1' }, ['ws://player:1']],
|
||||
[{ game_server_tcp: 'global:1', agentlist: [{ agentid: 11, game_server_tcp: 'agent:2' }] }, ['ws://agent:2']],
|
||||
] as const) {
|
||||
const servers = api.parseUrlServers(view(raw));
|
||||
assert.deepEqual(servers, expected); assert.ok(Object.isFrozen(servers));
|
||||
}
|
||||
});
|
||||
|
||||
test('parseUrlServers:拒绝空字符串、空数组和非字符串数组项', () => {
|
||||
assert.throws(() => parseUrlServers({ data: { urlserver: '' } }), /data\.urlserver/);
|
||||
assert.throws(() => parseUrlServers({ data: { urlserver: [] } }), /data\.urlserver/);
|
||||
assert.throws(() => parseUrlServers({ data: { urlserver: ['ok:1', ''] } }), /data\.urlserver/);
|
||||
assert.throws(() => parseUrlServers({ data: { urlserver: ['ok:1', 2] } }), /data\.urlserver/);
|
||||
});
|
||||
|
||||
test('parseUrlServers:显式拒绝已有的非 WebSocket scheme', () => {
|
||||
for (const value of [
|
||||
'http://config.example/ws',
|
||||
'https://config.example/ws',
|
||||
'ftp://config.example/ws',
|
||||
]) {
|
||||
assert.throws(
|
||||
() => parseUrlServers({ data: { urlserver: value } }),
|
||||
/不支持.*scheme/i,
|
||||
);
|
||||
test('missing or invalid chosen servers never fall back to obsolete envelope or player candidates', () => {
|
||||
for (const raw of [{ data: { urlserver: 'old:1' } }, {},
|
||||
...[[], ['ok:1', ''], ['ok:1', 2], true, ' ', 'http://wrong/ws', 'ws://', 'ws://user:pass@host:1', 'host:bad'].map(game_server_tcp => ({ game_server_tcp, player_server_tcp: 'valid:1' }))]) {
|
||||
assert.throws(() => api.parseUrlServers(view(raw)), api.ConfigParseError);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user