import { describe, it, expect, vi, afterEach } from 'vitest'; import Fastify from 'fastify'; import type { FastifyInstance } from 'fastify'; import { registerAuthRoutes } from '../src/routes/auth.js'; import { errorHandler } from '../src/middleware/error-handler.js'; import type { SafeUser } from '../src/repositories/user.repository.js'; let app: FastifyInstance; afterEach(async () => { if (app) await app.close(); }); function makeSafeUser(overrides?: Partial): SafeUser { return { id: 'user-1', email: 'michal@example.com', name: 'Michal', role: 'user', provider: 'local', externalId: null, version: 1, createdAt: new Date(), updatedAt: new Date(), ...overrides, }; } function createMockDeps() { return { authService: { login: vi.fn(async () => ({})), logout: vi.fn(async () => {}), findSession: vi.fn(async () => null), impersonate: vi.fn(async () => ({})), }, userService: { count: vi.fn(async () => 1), create: vi.fn(async () => makeSafeUser()), list: vi.fn(async () => []), getById: vi.fn(async () => makeSafeUser()), getByEmail: vi.fn(async () => makeSafeUser()), delete: vi.fn(async () => {}), }, groupService: { create: vi.fn(async () => ({})), list: vi.fn(async () => []), getById: vi.fn(async () => null), getByName: vi.fn(async () => null), update: vi.fn(async () => null), delete: vi.fn(async () => {}), }, rbacDefinitionService: { create: vi.fn(async () => ({})), list: vi.fn(async () => []), getById: vi.fn(async () => null), getByName: vi.fn(async () => null), update: vi.fn(async () => null), delete: vi.fn(async () => {}), }, rbacService: { canAccess: vi.fn(async () => false), canRunOperation: vi.fn(async () => false), getPermissions: vi.fn(async () => []), }, }; } describe('GET /api/v1/auth/me', () => { it('returns user identity for authenticated request', async () => { const deps = createMockDeps(); deps.authService.findSession.mockResolvedValue({ userId: 'user-1', expiresAt: new Date(Date.now() + 86400_000), }); deps.userService.getById.mockResolvedValue(makeSafeUser({ id: 'user-1', name: 'Michal', email: 'michal@example.com' })); app = Fastify({ logger: false }); app.setErrorHandler(errorHandler); registerAuthRoutes(app, deps as never); await app.ready(); const res = await app.inject({ method: 'GET', url: '/api/v1/auth/me', headers: { authorization: 'Bearer valid-token' }, }); expect(res.statusCode).toBe(200); const body = res.json<{ id: string; email: string; name: string | null }>(); expect(body.id).toBe('user-1'); expect(body.email).toBe('michal@example.com'); expect(body.name).toBe('Michal'); }); it('returns null name when user has no name', async () => { const deps = createMockDeps(); deps.authService.findSession.mockResolvedValue({ userId: 'user-1', expiresAt: new Date(Date.now() + 86400_000), }); deps.userService.getById.mockResolvedValue(makeSafeUser({ name: null })); app = Fastify({ logger: false }); app.setErrorHandler(errorHandler); registerAuthRoutes(app, deps as never); await app.ready(); const res = await app.inject({ method: 'GET', url: '/api/v1/auth/me', headers: { authorization: 'Bearer valid-token' }, }); expect(res.statusCode).toBe(200); expect(res.json<{ name: string | null }>().name).toBeNull(); }); it('returns 401 for unauthenticated request', async () => { const deps = createMockDeps(); app = Fastify({ logger: false }); app.setErrorHandler(errorHandler); registerAuthRoutes(app, deps as never); await app.ready(); const res = await app.inject({ method: 'GET', url: '/api/v1/auth/me', }); expect(res.statusCode).toBe(401); }); it('returns 401 for invalid token', async () => { const deps = createMockDeps(); deps.authService.findSession.mockResolvedValue(null); app = Fastify({ logger: false }); app.setErrorHandler(errorHandler); registerAuthRoutes(app, deps as never); await app.ready(); const res = await app.inject({ method: 'GET', url: '/api/v1/auth/me', headers: { authorization: 'Bearer bad-token' }, }); expect(res.statusCode).toBe(401); }); });