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

173 lines
6.0 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
assertLocalConnectionTarget,
assertLocalStartupContext,
resolveLocalStartup,
type LocalStartupContext,
} from '../../YouleNexus/assets/framework/config/local-startup.ts';
import { PROFILES } 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 & {
fetcher: ConfigFetcher & { calls: number };
} {
const fetcher = {
calls: 0,
async fetch(): Promise<never> {
this.calls += 1;
throw new Error('forbidden fetch');
},
};
return { mode: 'debug', hostKind: 'h5', win: {}, search, fetcher };
}
test('local debug resolves the authoritative direct config without fetching', async () => {
const input = options();
const context = await resolveLocalStartup(input);
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.equal(input.fetcher.calls, 0);
assert.equal(Object.isFrozen(context), true);
});
test('rejects non-local startup modes and hosts before fetching', async () => {
const cases: Array<[Partial<ResolveRuntimeConfigOptions>, RegExp]> = [
[{ mode: 'release' }, /mode.*debug/i],
[{ hostKind: 'native-settings' }, /hostKind.*h5/i],
[{ hostKind: 'uAgent_3' }, /hostKind.*h5/i],
];
for (const [override, expected] of cases) {
const base = options();
const input = { ...base, ...override };
await assert.rejects(resolveLocalStartup(input), expected);
assert.equal(base.fetcher.calls, 0);
}
});
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);
}
await resolveLocalStartup(options('?profile=local&mode=debug'));
});
test('rejects gameconfig and ambiguous identity controls before fetching', async () => {
const cases: Array<[string, RegExp]> = [
['?profile=local&gameconfig=remote-host', /gameconfig/i],
['?profile=local&agentid=A&agentid=B', /agentid/i],
['?profile=local&version=1&version=2', /version/i],
['?profile=local&gameid=other', /gameid/i],
];
for (const [search, expected] of cases) {
const input = options(search);
await assert.rejects(resolveLocalStartup(input), expected);
assert.equal(input.fetcher.calls, 0);
}
});
test('permits query identity overrides except gameid and validates numeric version', async () => {
const context = await resolveLocalStartup(options(
'?profile=local&agentid=A&channelid=C&marketid=9&version=12',
));
assert.deepEqual(context.config.identity, {
...BUILD_IDENTITY,
agentid: 'A',
channelid: 'C',
marketid: '9',
version: 12,
});
const invalid = options('?profile=local&version=not-a-number');
await assert.rejects(resolveLocalStartup(invalid), /identity.*version.*finite/i);
assert.equal(invalid.fetcher.calls, 0);
});
function validContext(): LocalStartupContext {
return {
hostKind: 'h5',
profile: 'local',
config: {
mode: 'debug',
isDebugger: true,
identity: BUILD_IDENTITY,
servers: PROFILES.local!.servers!,
source: 'direct',
gameserver: null,
rawConfig: null,
},
};
}
test('context validator rejects forged config fields and malformed identity', () => {
const base = validContext();
const configCases: Array<[keyof RuntimeConfig, unknown]> = [
['mode', 'release'],
['isDebugger', false],
['source', 'remote'],
['gameserver', 'https://example.com/config.txt'],
['rawConfig', {}],
['servers', ['ws://127.0.0.1:9999']],
['servers', ['https://127.0.0.1:3088']],
['servers', ['ws://user:pass@127.0.0.1:3088']],
];
for (const [field, value] of configCases) {
assert.throws(
() => assertLocalStartupContext({ ...base, config: { ...base.config, [field]: value } } as LocalStartupContext),
new RegExp(field, 'i'),
);
}
assert.throws(
() => assertLocalStartupContext({
...base,
config: { ...base.config, identity: { ...BUILD_IDENTITY, version: Number.NaN } },
}),
/identity.*version.*finite/i,
);
});
test('context validator names identity when a forged context omits the identity object', () => {
const base = validContext();
for (const identity of [null, undefined]) {
assert.throws(
() => assertLocalStartupContext({
...base,
config: { ...base.config, identity },
} as unknown as LocalStartupContext),
(error: unknown) => {
assert.ok(error instanceof Error);
assert.match(error.message, /identity/i);
assert.ok((error as Error & { cause?: unknown }).cause instanceof Error);
return true;
},
);
}
});
test('connection target must be a valid URL from the authoritative local profile', () => {
const context = validContext();
assert.doesNotThrow(() => assertLocalConnectionTarget(context, 'ws://127.0.0.1:3088'));
for (const [url, expected] of [
['ws://127.0.0.1:9999', /url|target|servers/i],
['ws://example.com:3088', /url|target|servers|hostname/i],
['not-a-url', /url/i],
] as const) {
assert.throws(() => assertLocalConnectionTarget(context, url), expected);
}
});