From 071c36916b9633a487ddaecd382bed3ba16a302f Mon Sep 17 00:00:00 2001 From: BOHA Date: Mon, 23 Mar 2026 09:13:05 +0100 Subject: [PATCH] test: add auth flow integration tests Co-Authored-By: Claude Opus 4.6 (1M context) --- src/__tests__/auth.test.ts | 48 ++++++++++++++++++++++++++++++++++++++ src/__tests__/helpers.ts | 26 +++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/__tests__/auth.test.ts create mode 100644 src/__tests__/helpers.ts diff --git a/src/__tests__/auth.test.ts b/src/__tests__/auth.test.ts new file mode 100644 index 0000000..10f6346 --- /dev/null +++ b/src/__tests__/auth.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { buildApp, extractCookie } from './helpers'; + +let app: Awaited>; + +beforeAll(async () => { app = await buildApp(); }); +afterAll(async () => { await app.close(); }); + +describe('POST /api/admin/login', () => { + it('returns 401 for invalid credentials', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/admin/login', + payload: { username: 'nonexistent', password: 'wrong' }, + }); + expect(res.statusCode).toBe(401); + expect(res.json().success).toBe(false); + }); + + it('returns 400 for missing fields', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/admin/login', + payload: {}, + }); + expect(res.statusCode).toBe(400); + }); +}); + +describe('POST /api/admin/refresh', () => { + it('returns 401 without refresh token', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/admin/refresh', + }); + expect(res.statusCode).toBe(401); + }); +}); + +describe('POST /api/admin/logout', () => { + it('clears refresh token cookie', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/admin/logout', + }); + expect(res.statusCode).toBeLessThan(500); + }); +}); diff --git a/src/__tests__/helpers.ts b/src/__tests__/helpers.ts new file mode 100644 index 0000000..a9904d0 --- /dev/null +++ b/src/__tests__/helpers.ts @@ -0,0 +1,26 @@ +import Fastify from 'fastify'; +import cookie from '@fastify/cookie'; +import rateLimit from '@fastify/rate-limit'; +import authRoutes from '../routes/admin/auth'; +import totpRoutes from '../routes/admin/totp'; + +export async function buildApp() { + const app = Fastify({ logger: false }); + await app.register(cookie); + await app.register(rateLimit, { max: 1000, timeWindow: '1 minute' }); + await app.register(authRoutes, { prefix: '/api/admin' }); + await app.register(totpRoutes, { prefix: '/api/admin/totp' }); + return app; +} + +export function extractCookie(response: any, name: string): string | undefined { + const cookies = response.headers['set-cookie']; + if (!cookies) return undefined; + const arr = Array.isArray(cookies) ? cookies : [cookies]; + for (const c of arr) { + if (c.startsWith(`${name}=`)) { + return c.split(';')[0].split('=')[1]; + } + } + return undefined; +}