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); }); });