diff --git a/src/app/api/auth/logout/route.test.ts b/src/app/api/auth/logout/route.test.ts new file mode 100644 index 000000000..c9bcfd8b4 --- /dev/null +++ b/src/app/api/auth/logout/route.test.ts @@ -0,0 +1,68 @@ +import { beforeEach, expect, test, vi } from 'vitest'; +import redis from '@/lib/redis'; +import { parseRequest } from '@/lib/request'; +import { POST } from './route'; + +vi.mock('@/lib/redis', () => ({ + default: { + enabled: true, + client: { + del: vi.fn(), + }, + }, +})); + +vi.mock('@/lib/request', () => ({ + parseRequest: vi.fn(), +})); + +vi.mock('@/lib/response', () => ({ + ok: () => new Response(null, { status: 200 }), +})); + +const redisMock = redis as { + enabled: boolean; + client: { + del: ReturnType; + }; +}; +const parseRequestMock = vi.mocked(parseRequest); + +beforeEach(() => { + redisMock.enabled = true; + redisMock.client.del.mockReset(); + parseRequestMock.mockReset(); +}); + +test('POST deletes the authenticated Redis auth key', async () => { + parseRequestMock.mockResolvedValue({ + auth: { authKey: 'auth:session-key' }, + error: undefined, + }); + + const response = await POST( + new Request('http://localhost/api/auth/logout', { + method: 'POST', + headers: { + authorization: 'Bearer secure-token', + }, + }), + ); + + expect(redisMock.client.del).toHaveBeenCalledTimes(1); + expect(redisMock.client.del).toHaveBeenCalledWith('auth:session-key'); + expect(redisMock.client.del).not.toHaveBeenCalledWith('secure-token'); + expect(response.status).toBe(200); +}); + +test('POST does not delete a key when auth fails', async () => { + parseRequestMock.mockResolvedValue({ + auth: null, + error: () => new Response(null, { status: 401 }), + }); + + const response = await POST(new Request('http://localhost/api/auth/logout', { method: 'POST' })); + + expect(redisMock.client.del).not.toHaveBeenCalled(); + expect(response.status).toBe(401); +}); diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index 153f1f523..5e593dde7 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -3,16 +3,14 @@ import { parseRequest } from '@/lib/request'; import { ok } from '@/lib/response'; export async function POST(request: Request) { - const { error } = await parseRequest(request); + const { auth, error } = await parseRequest(request); if (error) { return error(); } - if (redis.enabled) { - const token = request.headers.get('authorization')?.split(' ')?.[1]; - - await redis.client.del(token); + if (redis.enabled && auth?.authKey) { + await redis.client.del(auth.authKey); } return ok();