111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
'use strict';
|
|
|
|
import type { EventEmitter as EventEmitterType } from 'node:events';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { EventEmitter } = require('node:events');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const {
|
|
buildLogOnOptions,
|
|
createSteamLifecycle,
|
|
isRecoverableLoginError,
|
|
isUnrecoverableLoginError
|
|
} = require('../src/steam/lifecycle');
|
|
|
|
type LogOnOptions = {
|
|
accountName?: string;
|
|
password?: string;
|
|
refreshToken?: string;
|
|
logonID?: number;
|
|
steamID?: string;
|
|
};
|
|
|
|
type ScheduledTimer = {
|
|
fn: () => void;
|
|
delay: number;
|
|
};
|
|
|
|
type LifecycleTestUser = EventEmitterType & {
|
|
logOn: (options: LogOnOptions) => void;
|
|
webLogOn: () => void;
|
|
logOff: () => void;
|
|
};
|
|
|
|
test('buildLogOnOptions prefers refresh.token over account password', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'steam-chat-life-'));
|
|
const tokenPath = path.join(dir, 'refresh.token');
|
|
fs.writeFileSync(tokenPath, 'refresh-value\n');
|
|
|
|
assert.deepEqual(buildLogOnOptions({
|
|
accountName: 'account',
|
|
password: 'secret',
|
|
logonID: 42,
|
|
steamID: '7656'
|
|
}, tokenPath), {
|
|
refreshToken: 'refresh-value',
|
|
logonID: 42,
|
|
steamID: '7656'
|
|
});
|
|
});
|
|
|
|
test('login error classification separates recoverable and unrecoverable errors', () => {
|
|
assert.equal(isRecoverableLoginError(new Error('ECONNRESET socket closed')), true);
|
|
assert.equal(isRecoverableLoginError(new Error('InvalidPassword')), false);
|
|
assert.equal(isUnrecoverableLoginError(new Error('SteamGuard required')), true);
|
|
});
|
|
|
|
test('createSteamLifecycle logs on, resolves web session, saves refresh token, and avoids duplicate retry timers', async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'steam-chat-life-'));
|
|
const tokenPath = path.join(dir, 'refresh.token');
|
|
const user = new EventEmitter() as LifecycleTestUser;
|
|
const community = { cookies: null as string[] | null, setCookies(cookies: string[]) { this.cookies = cookies; } };
|
|
const logOnCalls: LogOnOptions[] = [];
|
|
let scheduled: ScheduledTimer | null = null;
|
|
let clearCount = 0;
|
|
user.logOn = (options: LogOnOptions) => logOnCalls.push(options);
|
|
user.webLogOn = () => user.emit('webSession', 'session-id', ['a=b']);
|
|
user.logOff = () => {};
|
|
|
|
const lifecycle = createSteamLifecycle({
|
|
steamUser: user,
|
|
steamCommunity: community,
|
|
config: { accountName: 'name', password: 'pass' },
|
|
refreshTokenPath: tokenPath,
|
|
logger: { info() {}, warn() {}, error() {} },
|
|
timers: {
|
|
setTimeout(fn: () => void, delay: number) {
|
|
scheduled = { fn, delay };
|
|
return 7;
|
|
},
|
|
clearTimeout() {
|
|
clearCount += 1;
|
|
}
|
|
}
|
|
});
|
|
|
|
lifecycle.start();
|
|
assert.equal(logOnCalls.length, 1);
|
|
assert.equal(logOnCalls[0].accountName, 'name');
|
|
|
|
user.emit('error', new Error('timeout'));
|
|
user.emit('error', new Error('timeout again'));
|
|
assert.ok(scheduled);
|
|
assert.equal(scheduled.delay, 5000);
|
|
scheduled.fn();
|
|
assert.equal(logOnCalls.length, 2);
|
|
|
|
user.emit('loggedOn');
|
|
await lifecycle.waitForLogin();
|
|
const webSession = await lifecycle.waitForWebSession();
|
|
assert.deepEqual(webSession, { sessionID: 'session-id', cookies: ['a=b'] });
|
|
assert.deepEqual(community.cookies, ['a=b']);
|
|
assert.equal(clearCount, 0);
|
|
|
|
user.emit('refreshToken', 'next-token');
|
|
assert.equal(fs.readFileSync(tokenPath, 'utf8'), 'next-token\n');
|
|
});
|