Files
youle_cocos/cocoscreator_projects/framework-tests/integration/local-login-sources.test.ts
T

122 lines
6.5 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { resolveLocalStartup } from '../../YouleNexus/assets/framework/config/local-startup.ts';
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
import { localAccountKey, type LocalStoragePort } from '../../YouleNexus/assets/framework/adapters/local/local-storage.ts';
import { createLocalLoginSources } from '../../YouleNexus/assets/framework/adapters/local/login-sources.ts';
import { buildLoginRequest } from '../../YouleNexus/assets/framework/protocol/contracts/index.ts';
class MapStorage implements LocalStoragePort {
readonly values = new Map<string, string>();
getItem(key: string): string | null { return this.values.get(key) ?? null; }
setItem(key: string, value: string): void { this.values.set(key, value); }
}
function resolve(search = '?profile=local') {
return resolveLocalStartup({ mode: 'debug', hostKind: 'h5', win: {}, search,
fetcher: { async fetch(): Promise<never> { throw new Error('forbidden fetch'); } } });
}
function sourcesFor(context: Awaited<ReturnType<typeof resolve>>, storage: LocalStoragePort,
overrides: Partial<Parameters<typeof createLocalLoginSources>[0]> = {}) {
return createLocalLoginSources({ context, storage, random: () => 0.5, now: () => 123456,
sampleHost: () => ({ location: null }), isSessionActive: () => false, ...overrides });
}
test('real local resolution and visitor sources build the login envelope with query identity and omitted optional fields', async () => {
const context = await resolve('?profile=local&version=731&agentid=integration-agent&channelid=integration-channel&marketid=009');
const sources = sourcesFor(context, new MapStorage());
const account = await sources.prepareLogin();
const envelope = buildLoginRequest(context.config, account, sources.getLoginDeviceSnapshot());
assert.equal(envelope.app, 'youle');
assert.equal(envelope.route, 'agent');
assert.equal(envelope.rpc, 'player_login');
assert.equal(envelope.data.gameid, BUILD_IDENTITY.gameid);
assert.equal(envelope.data.version, 731);
assert.equal(typeof envelope.data.version, 'number');
assert.equal(envelope.data.agentid, 'integration-agent');
assert.equal(envelope.data.channelid, 'integration-channel');
assert.equal(envelope.data.marketid, '009');
assert.equal(envelope.data.openid, 'testopenid_50000');
assert.equal(envelope.data.unionid, 'ylgame50000');
assert.equal(envelope.data.province, 'jiangxi');
assert.equal(envelope.data.city, 'nanchang');
assert.equal(envelope.data.location, null);
assert.equal(envelope.data.machineid, '123456-5499');
for (const field of ['playerid', 'telphone', 'telphoneAuto', 'ip']) {
assert.equal(Object.prototype.hasOwnProperty.call(envelope.data, field), false, field);
}
});
test('one host sample overrides province and city in the real envelope without changing the visitor cache', async () => {
const context = await resolve();
const storage = new MapStorage();
const location = { longitude: 115.9, latitude: 28.7 };
const city = { ip: '10.0.0.2', province: 'P', city: 'C' };
let samples = 0;
const sources = sourcesFor(context, storage, {
sampleHost: () => { samples++; return { location, returnCitySN: city }; },
});
const account = await sources.prepareLogin();
city.ip = 'changed'; city.province = 'changed'; city.city = 'changed';
const envelope = buildLoginRequest(context.config, account, sources.getLoginDeviceSnapshot());
assert.equal(samples, 1);
assert.equal(envelope.data.province, 'P');
assert.equal(envelope.data.city, 'C');
assert.equal(envelope.data.ip, '10.0.0.2');
assert.strictEqual(envelope.data.location, location);
const cached = JSON.parse(storage.getItem(localAccountKey(context))!);
assert.equal(cached.account.province, 'jiangxi');
assert.equal(cached.account.city, 'nanchang');
});
test('a version upgrade reuses stored account and machine but sends the newly resolved numeric version', async () => {
const storage = new MapStorage();
const firstContext = await resolve('?profile=local&version=731');
const firstSources = sourcesFor(firstContext, storage);
const firstAccount = await firstSources.prepareLogin();
const first = buildLoginRequest(firstContext.config, firstAccount, firstSources.getLoginDeviceSnapshot());
const originalCache = new Map(storage.values);
const nextContext = await resolve('?profile=local&version=732');
const nextSources = sourcesFor(nextContext, storage, {
random: () => { throw new Error('cached login must not regenerate identity'); },
now: () => { throw new Error('cached login must not regenerate machine'); },
});
const nextAccount = await nextSources.prepareLogin();
const next = buildLoginRequest(nextContext.config, nextAccount, nextSources.getLoginDeviceSnapshot());
assert.equal(first.data.version, 731);
assert.equal(next.data.version, 732);
assert.deepEqual(nextAccount, firstAccount);
assert.equal(next.data.openid, first.data.openid);
assert.equal(next.data.machineid, first.data.machineid);
assert.deepEqual(storage.values, originalCache);
});
test('corrupt and unknown cached account records stop before the real builder and invalidate the prepared device', async (t) => {
const context = await resolve();
const validAccount = { openid: 'cached-user', nickname: 'Visitor', avatar: '', sex: 1,
province: 'P', city: 'C', unionid: 'cached-union' };
for (const [name, raw, error] of [
['corrupt JSON', '{broken', /invalid JSON/],
['unknown schema', JSON.stringify({ schemaVersion: 2, account: validAccount }), /schemaVersion/],
['unknown account field', JSON.stringify({ schemaVersion: 1, account: { ...validAccount, headimgurl: 'legacy' } }), /headimgurl/],
['missing identity', JSON.stringify({ schemaVersion: 1, account: { ...validAccount, openid: '' } }), /openid/],
] as const) {
await t.test(name, async () => {
const storage = new MapStorage();
const sources = sourcesFor(context, storage);
await sources.prepareLogin();
storage.setItem(localAccountKey(context), raw);
let builderReached = false;
await assert.rejects(async () => {
const account = await sources.prepareLogin();
builderReached = true;
return buildLoginRequest(context.config, account, sources.getLoginDeviceSnapshot());
}, error);
assert.equal(builderReached, false);
assert.throws(() => sources.getLoginDeviceSnapshot(), /prepare/i);
assert.equal(storage.getItem(localAccountKey(context)), raw);
});
}
});