Files
RedditBot/tests/account-auth-context.test.ts
T

105 lines
3.5 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { AccountAuthContext } from '../src/services/account-auth-context.js';
import { createMockLogger } from './mockLogger.js';
describe('AccountAuthContext', () => {
it('fails closed until Reddit and Matrix resolve to one authenticated account', async () => {
const auth = new AccountAuthContext(createMockLogger());
const authenticate = vi.fn().mockResolvedValue({
accountId: '@t2_account:reddit.com',
redditUsername: 'account',
matrixUserId: '@t2_account:reddit.com',
});
auth.setAuthenticator(authenticate);
expect(auth.isFullyAuthenticated()).toBe(false);
expect(auth.getSnapshot().status).toBe('login-required');
await expect(auth.ensureFullyAuthenticated()).resolves.toBe(true);
expect(auth.isFullyAuthenticated()).toBe(true);
expect(auth.getSnapshot()).toMatchObject({
status: 'authenticated',
accountId: '@t2_account:reddit.com',
});
});
it('coalesces concurrent authentication checks globally', async () => {
const auth = new AccountAuthContext(createMockLogger());
let resolveAuth: ((account: {
accountId: string;
redditUsername: string;
matrixUserId: string;
}) => void) | undefined;
const authenticate = vi.fn(() => new Promise((resolve) => { resolveAuth = resolve; }));
auth.setAuthenticator(authenticate);
const first = auth.ensureFullyAuthenticated();
const second = auth.ensureFullyAuthenticated();
await vi.waitFor(() => expect(authenticate).toHaveBeenCalledOnce());
resolveAuth?.({
accountId: '@t2_account:reddit.com',
redditUsername: 'account',
matrixUserId: '@t2_account:reddit.com',
});
await expect(Promise.all([first, second])).resolves.toEqual([true, true]);
});
it('invalidates the whole account context when either login layer expires', async () => {
const auth = new AccountAuthContext(createMockLogger());
auth.markAuthenticated({
accountId: '@t2_account:reddit.com',
redditUsername: 'account',
matrixUserId: '@t2_account:reddit.com',
});
auth.invalidate('matrix token rejected');
expect(auth.isFullyAuthenticated()).toBe(false);
expect(auth.getSnapshot()).toMatchObject({
status: 'login-required',
accountId: null,
reason: 'matrix token rejected',
});
});
it('fails closed when the authentication lease expires', () => {
let now = 1_000;
const auth = new AccountAuthContext(createMockLogger(), {
leaseMs: 100,
retryDelayMs: 1_000,
now: () => now,
});
auth.markAuthenticated({
accountId: '@t2_account:reddit.com',
redditUsername: 'account',
matrixUserId: '@t2_account:reddit.com',
});
now = 1_101;
expect(auth.isFullyAuthenticated()).toBe(false);
expect(auth.getSnapshot().status).toBe('login-required');
});
it('throttles failed global authentication checks', async () => {
let now = 1_000;
const auth = new AccountAuthContext(createMockLogger(), {
leaseMs: 100,
retryDelayMs: 1_000,
now: () => now,
});
const authenticate = vi.fn().mockResolvedValue(null);
auth.setAuthenticator(authenticate);
await expect(auth.ensureFullyAuthenticated()).resolves.toBe(false);
await expect(auth.ensureFullyAuthenticated()).resolves.toBe(false);
expect(authenticate).toHaveBeenCalledOnce();
now = 2_001;
await auth.ensureFullyAuthenticated();
expect(authenticate).toHaveBeenCalledTimes(2);
});
});