/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { CachedSession, mergeConfig, SessionCacheStrategy } from '@vendure/core'; import { createTestEnvironment } from '@vendure/testing'; import gql from 'graphql-tag'; import path from 'path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { vi } from 'vitest'; import { initialData } from '../../../e2e-common/e2e-initial-data'; import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config'; import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '../../common/src/shared-constants'; import { AttemptLoginMutation, AttemptLoginMutationVariables, MeQuery, } from './graphql/generated-e2e-admin-types'; import { ATTEMPT_LOGIN, ME } from './graphql/shared-definitions'; const testSessionCache = new Map(); const getSpy = vi.fn(); const setSpy = vi.fn(); const clearSpy = vi.fn(); const deleteSpy = vi.fn(); class TestingSessionCacheStrategy implements SessionCacheStrategy { clear() { clearSpy(); testSessionCache.clear(); } delete(sessionToken: string) { deleteSpy(sessionToken); testSessionCache.delete(sessionToken); } get(sessionToken: string) { getSpy(sessionToken); return testSessionCache.get(sessionToken); } set(session: CachedSession) { setSpy(session); testSessionCache.set(session.token, session); } } describe('Session caching', () => { const { server, adminClient } = createTestEnvironment( mergeConfig(testConfig(), { authOptions: { sessionCacheStrategy: new TestingSessionCacheStrategy(), sessionCacheTTL: 2, }, }), ); beforeAll(async () => { await server.init({ initialData, productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), customerCount: 1, }); testSessionCache.clear(); }, TEST_SETUP_TIMEOUT_MS); afterAll(async () => { await server.destroy(); }); it('populates the cache on login', async () => { setSpy.mockClear(); expect(setSpy.mock.calls.length).toBe(0); expect(testSessionCache.size).toBe(0); await adminClient.query(ATTEMPT_LOGIN, { username: SUPER_ADMIN_USER_IDENTIFIER, password: SUPER_ADMIN_USER_PASSWORD, }); expect(testSessionCache.size).toBe(1); expect(setSpy.mock.calls.length).toBe(1); }); it('takes user data from cache on next request', async () => { getSpy.mockClear(); const { me } = await adminClient.query(ME); expect(getSpy.mock.calls.length).toBe(1); }); it('sets fresh data after TTL expires', async () => { setSpy.mockClear(); await adminClient.query(ME); expect(setSpy.mock.calls.length).toBe(0); await adminClient.query(ME); expect(setSpy.mock.calls.length).toBe(0); await pause(2000); await adminClient.query(ME); expect(setSpy.mock.calls.length).toBe(1); }); it('clears cache for that user on logout', async () => { deleteSpy.mockClear(); expect(deleteSpy.mock.calls.length).toBe(0); await adminClient.query( gql` mutation Logout { logout { success } } `, ); expect(testSessionCache.size).toBe(0); expect(deleteSpy.mock.calls.length).toBeGreaterThan(0); }); }); describe('Session expiry', () => { const { server, adminClient } = createTestEnvironment( mergeConfig(testConfig(), { authOptions: { sessionDuration: '3s', sessionCacheTTL: 1, }, }), ); beforeAll(async () => { await server.init({ initialData, productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), customerCount: 1, }); await adminClient.asSuperAdmin(); }, TEST_SETUP_TIMEOUT_MS); afterAll(async () => { await server.destroy(); }); it('session does not expire with continued use', async () => { await adminClient.asSuperAdmin(); await pause(1000); await adminClient.query(ME); await pause(1000); await adminClient.query(ME); await pause(1000); await adminClient.query(ME); await pause(1000); await adminClient.query(ME); }, 10000); it('session expires when not used for longer than sessionDuration', async () => { await adminClient.asSuperAdmin(); await pause(3500); try { await adminClient.query(ME); fail('Should have thrown'); } catch (e: any) { expect(e.message).toContain('You are not currently authorized to perform this action'); } }, 10000); }); function pause(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); }