import { describe, expect, it, vi } from 'vitest'; import os from 'node:os'; import path from 'node:path'; import { ChatService } from '../src/services/chat/service.js'; import { ChatCache } from '../src/services/chat/cache.js'; import { MatrixClient } from '../src/services/chat/matrix/client.js'; import { createMockLogger } from './mockLogger.js'; describe('ChatService account authentication gate', () => { it('does not make Matrix requests before full account authentication', async () => { const logger = createMockLogger(); const matrix = new MatrixClient({ homeserver: 'https://matrix.example.test', accessToken: 'cached-but-unverified', userId: '@t2_account:reddit.com', capturedAt: 1, }, logger, { minRequestIntervalMs: 0 }); matrix.sync = vi.fn(); const cache = new ChatCache( logger, path.join(os.tmpdir(), `chat-auth-${Date.now()}-${Math.random()}.json`), ); const auth = { isFullyAuthenticated: vi.fn(() => false) }; const service = new ChatService( matrix, logger, undefined, undefined, undefined, undefined, cache, undefined, undefined, auth, ); await expect(service.refreshTopChats()).rejects.toThrow( 'Matrix authentication could not be restored', ); expect(matrix.sync).not.toHaveBeenCalled(); }); it('coalesces concurrent delta refresh requests before they reach Matrix', async () => { const logger = createMockLogger(); const matrix = new MatrixClient({ homeserver: 'https://matrix.example.test', accessToken: 'verified', userId: '@t2_account:reddit.com', capturedAt: 1, }, logger, { minRequestIntervalMs: 0 }); let finishSync!: (value: { next_batch: string }) => void; matrix.sync = vi.fn(() => new Promise((resolve) => { finishSync = resolve; })) as typeof matrix.sync; const cache = new ChatCache( logger, path.join(os.tmpdir(), `chat-auth-${Date.now()}-${Math.random()}.json`), ); const service = new ChatService( matrix, logger, undefined, undefined, undefined, undefined, cache, undefined, undefined, { isFullyAuthenticated: () => true, invalidate: vi.fn() }, ); const first = service.refreshTopChats(); const second = service.refreshTopChats(); await vi.waitFor(() => expect(matrix.sync).toHaveBeenCalledOnce()); finishSync({ next_batch: 's1' }); await expect(Promise.all([first, second])).resolves.toEqual([0, 0]); expect(matrix.sync).toHaveBeenCalledOnce(); }); it('reports failed sync termination to progress listeners', async () => { const logger = createMockLogger(); const matrix = new MatrixClient({ homeserver: 'https://matrix.example.test', accessToken: 'unverified', userId: '@t2_account:reddit.com', capturedAt: 1, }, logger, { minRequestIntervalMs: 0 }); const service = new ChatService( matrix, logger, undefined, undefined, undefined, undefined, new ChatCache(logger, path.join(os.tmpdir(), `chat-auth-${Date.now()}-${Math.random()}.json`)), undefined, undefined, { isFullyAuthenticated: () => false, invalidate: vi.fn() }, ); const listener = vi.fn(); service.setProgressListener(listener); await expect(service.refreshTopChats()).rejects.toThrow(); expect(listener).toHaveBeenLastCalledWith(null, { status: 'failed', error: 'Matrix authentication could not be restored', }); }); });