Files
RedditBot/tests/matrix-token-refresher.test.ts
T

99 lines
3.5 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import type { BrowserContext } from 'playwright';
import { createMockLogger } from './mockLogger.js';
import {
MatrixRemoteLoginRequiredError,
MatrixTokenRefresher,
} from '../src/services/chat/matrix/token-refresher.js';
import { MatrixBrowserLoginRequiredError } from '../src/services/chat/matrix/token-extractor.js';
import type { MatrixCredentials } from '../src/services/chat/matrix/types.js';
const credentials: MatrixCredentials = {
homeserver: 'https://matrix.redditspace.com',
accessToken: 'fresh-token',
userId: '@t2_bot:reddit.com',
capturedAt: 123,
};
function setup(options: { sessionValid?: boolean; loginSucceeds?: boolean } = {}) {
const context = {} as BrowserContext;
const browser = {
getContext: vi.fn(() => context),
isSessionValid: vi.fn(async () => options.sessionValid ?? true),
login: vi.fn(async () => options.loginSucceeds ?? true),
};
const extract = vi.fn(async () => credentials);
const applyCredentials = vi.fn();
const logger = createMockLogger();
const refresher = new MatrixTokenRefresher(
browser,
logger,
applyCredentials,
extract,
);
return { refresher, browser, context, extract, applyCredentials, logger };
}
describe('MatrixTokenRefresher', () => {
it('repairs an expired Reddit browser session before capturing a Matrix token', async () => {
const { refresher, browser, context, extract, applyCredentials } = setup({
sessionValid: false,
loginSucceeds: true,
});
await refresher.refresh();
expect(browser.login).toHaveBeenCalledOnce();
expect(extract).toHaveBeenCalledWith(context, expect.anything());
expect(applyCredentials).toHaveBeenCalledWith(credentials);
});
it('fails fast with remote-login guidance when the Reddit session cannot be restored', async () => {
const { refresher, browser, extract } = setup({
sessionValid: false,
loginSucceeds: false,
});
await expect(refresher.refresh()).rejects.toBeInstanceOf(MatrixRemoteLoginRequiredError);
await expect(refresher.refresh()).rejects.toThrow(/dashboard.*noVNC/i);
expect(browser.login).toHaveBeenCalledTimes(2);
expect(extract).not.toHaveBeenCalled();
});
it('coalesces concurrent refreshes so only one browser capture runs', async () => {
const { refresher, extract, applyCredentials } = setup();
let release: ((value: MatrixCredentials) => void) | undefined;
extract.mockImplementation(
() => new Promise<MatrixCredentials>((resolve) => { release = resolve; }),
);
const first = refresher.refresh();
const second = refresher.refresh();
await vi.waitFor(() => expect(extract).toHaveBeenCalledOnce());
release?.(credentials);
await Promise.all([first, second]);
expect(applyCredentials).toHaveBeenCalledOnce();
});
it('repairs and retries when chat redirects to login despite a valid Reddit API session', async () => {
const { refresher, browser, context, extract, applyCredentials } = setup({
sessionValid: true,
loginSucceeds: true,
});
extract
.mockRejectedValueOnce(
new MatrixBrowserLoginRequiredError('https://www.reddit.com/login/?dest=/chat/'),
)
.mockResolvedValueOnce(credentials);
await refresher.refresh();
expect(browser.login).toHaveBeenCalledOnce();
expect(extract).toHaveBeenCalledTimes(2);
expect(extract).toHaveBeenNthCalledWith(2, context, expect.anything());
expect(applyCredentials).toHaveBeenCalledWith(credentials);
});
});