CI / test (ubuntu-latest, node 22) (push) Failing after 33m10s
CI / test (ubuntu-latest, node 24) (push) Failing after 34m41s
CI / tooling (lint, imports, typecheck) (push) Failing after 36m11s
CI / test (windows-latest, node 22) (push) Canceled after 0s
CI / test (windows-latest, node 24) (push) Canceled after 0s
69 lines
4.2 KiB
JavaScript
69 lines
4.2 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createBindingApi, ensureBindingSession, preferredRole, roleKey } from '../../public/js/skland/api.js';
|
|
|
|
test('the title client puts its game token only in an authorization header and never sends a player id', async () => {
|
|
const calls = [];
|
|
const request = createBindingApi({ token: 'private-game-token', fetchImpl: async (url, init) => {
|
|
calls.push({ url, init }); return { ok: true, status: 200, json: async () => ({ ok: true }) };
|
|
} });
|
|
await request('status'); await request('password/login', { phone: '13800000000', password: 'local-input' });
|
|
assert.equal(calls[0].url, '/skland/session/status');
|
|
assert.equal(calls[0].init.headers.authorization, 'Bearer private-game-token');
|
|
assert.equal(calls[0].init.method, 'GET');
|
|
assert.equal(calls[1].init.method, 'POST');
|
|
assert.deepEqual(JSON.parse(calls[1].init.body), { phone: '13800000000', password: 'local-input' });
|
|
assert.ok(calls.every(({ url, init }) => !url.includes('private-game-token') && !init.body?.includes('private-game-token')));
|
|
});
|
|
|
|
test('the title client distinguishes disabled module, expired session, network and malformed responses', async () => {
|
|
for (const [status, code] of [[404, 'off'], [401, 'session']]) {
|
|
await assert.rejects(createBindingApi({ token: 'T', fetchImpl: async () => ({ status }) })('status'), { code });
|
|
}
|
|
await assert.rejects(createBindingApi()('status'), { code: 'session' });
|
|
await assert.rejects(createBindingApi({ token: 'T', fetchImpl: async () => { throw new Error('offline'); } })('status'), { code: 'network' });
|
|
await assert.rejects(createBindingApi({ token: 'T', fetchImpl: async () => ({ status: 200, ok: true, json: async () => { throw new Error(); } }) })('status'), { code: 'response' });
|
|
});
|
|
|
|
test('binding starts hello without entering the game, reuses an online identity, and cleans up on failure', async () => {
|
|
const listeners = new Map();
|
|
let current = null, connects = 0, sentName;
|
|
const identity = { init: async () => {}, getToken: () => current };
|
|
const net = {
|
|
status: 'connected',
|
|
on: (event, fn) => { listeners.set(event, fn); return () => listeners.delete(event); },
|
|
setName: (name) => { sentName = name; },
|
|
connect: () => { connects++; setTimeout(() => { current = 'T'; listeners.get('welcome')?.({ token: 'T' }); }, 0); },
|
|
};
|
|
assert.equal(await ensureBindingSession({ net, identity, name: '博士代号' }), 'T');
|
|
assert.equal(sentName, '博士代号'); assert.equal(listeners.size, 0);
|
|
net.status = 'online';
|
|
assert.equal(await ensureBindingSession({ net, identity, name: '另一个名字' }), 'T');
|
|
assert.equal(connects, 1);
|
|
net.status = 'connected'; net.connect = () => {};
|
|
await assert.rejects(ensureBindingSession({ net, identity, name: '博士', timeoutMs: 1 }), { code: 'network' });
|
|
assert.equal(listeners.size, 0);
|
|
});
|
|
|
|
test('nickname selection uses the default role, preserves a selected channel, and supports no roles', () => {
|
|
const a = { uid: '123', channelName: '官服', gameId: 1, nickName: '官服博士' };
|
|
const b = { uid: '123', channelName: 'B服', gameId: 2, nickName: 'B服博士', isDefault: true };
|
|
assert.equal(preferredRole([a, b]), b);
|
|
assert.equal(preferredRole([a, b], roleKey(a)), a);
|
|
assert.equal(preferredRole([a, b], 'removed-role'), b);
|
|
assert.equal(preferredRole([a]), a);
|
|
assert.equal(preferredRole([]), null);
|
|
assert.equal(preferredRole([{ uid: 'missing-name', isDefault: true }, { ...b, nickName: ' ' }, a]), a);
|
|
assert.equal(preferredRole([{ uid: 'missing-name' }]), null);
|
|
assert.equal(preferredRole([{ uid: null, channelName: '官服', nickName: '缺少身份' }]), null);
|
|
assert.equal(roleKey(a), roleKey({ ...a, uid: 123, gameId: null, nickName: '改名以后' }), 'nickname and optional gameId never change identity');
|
|
});
|
|
|
|
|
|
test('an expired recovery is distinguishable from a transport error and never becomes a successful entry', async () => {
|
|
const request = createBindingApi({ token: 'T', fetchImpl: async () => ({ status: 409, ok: false,
|
|
json: async () => ({ ok: false, code: 'resume_expired', error: 'expired' }),
|
|
}) });
|
|
await assert.rejects(request('resume', { id: 'stale' }), { code: 'resume_expired' });
|
|
});
|