import IORedis from 'ioredis'; import { adaptIoredisForConnectRedis } from './ioredis-connect-redis-adapter'; /** * Unit-level tests that pin the **shape** of each adapted call — * `connect-redis` v9 invokes them in a specific way and any drift * breaks the session store silently. The behavioural round-trip is * exercised by `connect-redis`' own integration tests once its * `RedisStore` is wired in `session.module.spec.ts`. */ describe('adaptIoredisForConnectRedis', () => { function fakeClient(): { spy: Record; client: IORedis; } { const spy = { get: jest.fn().mockResolvedValue('v'), set: jest.fn().mockResolvedValue('OK'), expire: jest.fn().mockResolvedValue(1), del: jest.fn().mockResolvedValue(2), mget: jest.fn().mockResolvedValue(['v1', null, 'v3']), scanStream: jest.fn(), }; // The adapter only touches the 6 methods above — we deliberately // don't construct a real `ioredis` instance to keep the unit // pure and avoid hitting the network. return { spy, client: spy as unknown as IORedis }; } it('forwards get(key) unchanged', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); await adapter.get('sid'); expect(spy['get']).toHaveBeenCalledWith('sid'); }); it('translates set with EX expiration to ioredis varargs', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); await adapter.set('sid', 'value', { expiration: { type: 'EX', value: 1800 } }); expect(spy['set']).toHaveBeenCalledWith('sid', 'value', 'EX', 1800); }); it('translates set without options to a plain ioredis set', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); await adapter.set('sid', 'value'); expect(spy['set']).toHaveBeenCalledWith('sid', 'value'); }); it('forwards expire(key, seconds) unchanged', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); await adapter.expire('sid', 1800); expect(spy['expire']).toHaveBeenCalledWith('sid', 1800); }); it('spreads del([keys]) into ioredis varargs', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); await adapter.del(['a', 'b', 'c']); expect(spy['del']).toHaveBeenCalledWith('a', 'b', 'c'); }); it('short-circuits del([]) without touching the client', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); const result = await adapter.del([]); expect(result).toBe(0); expect(spy['del']).not.toHaveBeenCalled(); }); it('spreads mGet([keys]) into ioredis varargs and returns the array', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); const result = await adapter.mGet(['a', 'b', 'c']); expect(spy['mget']).toHaveBeenCalledWith('a', 'b', 'c'); expect(result).toEqual(['v1', null, 'v3']); }); it('short-circuits mGet([]) without touching the client', async () => { const { spy, client } = fakeClient(); const adapter = adaptIoredisForConnectRedis(client); expect(await adapter.mGet([])).toEqual([]); expect(spy['mget']).not.toHaveBeenCalled(); }); it('wraps scanStream into an async iterable of key arrays', async () => { const { spy, client } = fakeClient(); const fakeStream = makeReadable([['session:a', 'session:b'], ['session:c']]); (spy['scanStream'] as jest.Mock).mockReturnValueOnce(fakeStream); const adapter = adaptIoredisForConnectRedis(client); const iterator = adapter.scanIterator({ MATCH: 'session:*', COUNT: 100 }); const collected: string[][] = []; for await (const batch of iterator) { collected.push(batch); } expect(spy['scanStream']).toHaveBeenCalledWith({ match: 'session:*', count: 100 }); expect(collected).toEqual([['session:a', 'session:b'], ['session:c']]); }); }); function makeReadable(chunks: T[]): NodeJS.ReadableStream { // Minimal async iterable that mimics `ioredis.scanStream`'s // for-await contract — the adapter only needs `Symbol.asyncIterator`. return { async *[Symbol.asyncIterator]() { for (const chunk of chunks) { yield chunk; } }, } as unknown as NodeJS.ReadableStream; }