From b20673e7eac3e97a17622ff1db6a246be28993fe Mon Sep 17 00:00:00 2001 From: Orvanix Date: Thu, 12 Mar 2026 12:20:41 +0000 Subject: [PATCH] test(auth): add tests for token authentication --- tests/api/admin-login-token.routes.test.ts | 106 +++++++++++++++++++++ tests/api/auth-login-token.routes.test.ts | 73 ++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 tests/api/admin-login-token.routes.test.ts create mode 100644 tests/api/auth-login-token.routes.test.ts diff --git a/tests/api/admin-login-token.routes.test.ts b/tests/api/admin-login-token.routes.test.ts new file mode 100644 index 0000000..bd5af3c --- /dev/null +++ b/tests/api/admin-login-token.routes.test.ts @@ -0,0 +1,106 @@ +/** + * Component: Admin User Login Token Tests + * Documentation: documentation/testing.md + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createPrismaMock } from '../helpers/prisma'; + +let authRequest: any; + +const prismaMock = createPrismaMock(); +const requireAuthMock = vi.hoisted(() => vi.fn()); +const requireAdminMock = vi.hoisted(() => vi.fn()); +const generateApiTokenMock = vi.hoisted(() => vi.fn()); + +vi.mock('@/lib/db', () => ({ + prisma: prismaMock, +})); + +vi.mock('@/lib/middleware/auth', () => ({ + requireAuth: requireAuthMock, + requireAdmin: requireAdminMock, +})); + +vi.mock('@/lib/utils/api-token', () => ({ + generateApiToken: generateApiTokenMock, +})); + +describe('Admin login token routes', () => { + beforeEach(() => { + vi.clearAllMocks(); + authRequest = { user: { id: 'admin-1', username: 'admin', role: 'admin' }, json: vi.fn() }; + requireAuthMock.mockImplementation((_req: any, handler: any) => handler(authRequest)); + requireAdminMock.mockImplementation((_req: any, handler: any) => handler()); + generateApiTokenMock.mockReturnValue({ fullToken: 'rmab_test_token' }); + }); + + describe('POST /api/admin/users/[id]/login-token', () => { + it('generates a login token for an active user', async () => { + prismaMock.user.findUnique.mockResolvedValueOnce({ + plexUsername: 'testuser', + deletedAt: null, + }); + prismaMock.user.update.mockResolvedValueOnce({}); + + const { POST } = await import('@/app/api/admin/users/[id]/login-token/route'); + const response = await POST({} as any, { params: Promise.resolve({ id: 'u1' }) }); + const payload = await response.json(); + + expect(response.status).toBe(201); + expect(payload.fullToken).toBe('rmab_test_token'); + }); + + it('returns 404 when user does not exist', async () => { + prismaMock.user.findUnique.mockResolvedValueOnce(null); + + const { POST } = await import('@/app/api/admin/users/[id]/login-token/route'); + const response = await POST({} as any, { params: Promise.resolve({ id: 'missing' }) }); + const payload = await response.json(); + + expect(response.status).toBe(404); + expect(payload.error).toMatch(/User not found/); + }); + + it('returns 403 when user is deleted', async () => { + prismaMock.user.findUnique.mockResolvedValueOnce({ + plexUsername: 'deleteduser', + deletedAt: new Date(), + }); + + const { POST } = await import('@/app/api/admin/users/[id]/login-token/route'); + const response = await POST({} as any, { params: Promise.resolve({ id: 'u2' }) }); + const payload = await response.json(); + + expect(response.status).toBe(403); + expect(payload.error).toMatch(/deleted user/); + }); + }); + + describe('DELETE /api/admin/users/[id]/login-token', () => { + it('revokes the login token for a user', async () => { + prismaMock.user.findUnique.mockResolvedValueOnce({ + plexUsername: 'testuser', + }); + prismaMock.user.update.mockResolvedValueOnce({}); + + const { DELETE } = await import('@/app/api/admin/users/[id]/login-token/route'); + const response = await DELETE({} as any, { params: Promise.resolve({ id: 'u1' }) }); + const payload = await response.json(); + + expect(response.status).toBe(200); + expect(payload.success).toBe(true); + }); + + it('returns 404 when user does not exist', async () => { + prismaMock.user.findUnique.mockResolvedValueOnce(null); + + const { DELETE } = await import('@/app/api/admin/users/[id]/login-token/route'); + const response = await DELETE({} as any, { params: Promise.resolve({ id: 'missing' }) }); + const payload = await response.json(); + + expect(response.status).toBe(404); + expect(payload.error).toMatch(/User not found/); + }); + }); +}); diff --git a/tests/api/auth-login-token.routes.test.ts b/tests/api/auth-login-token.routes.test.ts new file mode 100644 index 0000000..861aca2 --- /dev/null +++ b/tests/api/auth-login-token.routes.test.ts @@ -0,0 +1,73 @@ +/** + * Component: Token Login Route Tests + * Documentation: documentation/testing.md + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createPrismaMock } from '../helpers/prisma'; + +const prismaMock = createPrismaMock(); +const generateAccessTokenMock = vi.hoisted(() => vi.fn()); +const generateRefreshTokenMock = vi.hoisted(() => vi.fn()); + +vi.mock('@/lib/db', () => ({ + prisma: prismaMock, +})); + +vi.mock('@/lib/utils/jwt', () => ({ + generateAccessToken: generateAccessTokenMock, + generateRefreshToken: generateRefreshTokenMock, +})); + +describe('GET /api/auth/token/login', () => { + beforeEach(() => { + vi.clearAllMocks(); + generateAccessTokenMock.mockReturnValue('access-token'); + generateRefreshTokenMock.mockReturnValue('refresh-token'); + }); + + it('authenticates user with a valid token', async () => { + prismaMock.user.findFirst.mockResolvedValueOnce({ + id: 'u1', + plexId: 'plex-1', + plexUsername: 'testuser', + plexEmail: 'test@example.com', + avatarUrl: null, + role: 'user', + }); + prismaMock.user.update.mockResolvedValueOnce({}); + + const { GET } = await import('@/app/api/auth/token/login/route'); + const request = { nextUrl: { searchParams: new URLSearchParams('token=rmab_valid_token') } }; + const response = await GET(request as any); + const payload = await response.json(); + + expect(response.status).toBe(200); + expect(payload.accessToken).toBe('access-token'); + expect(payload.refreshToken).toBe('refresh-token'); + expect(payload.user.username).toBe('testuser'); + expect(payload.user.email).toBe('test@example.com'); + }); + + it('returns 400 when token parameter is missing', async () => { + const { GET } = await import('@/app/api/auth/token/login/route'); + const request = { nextUrl: { searchParams: new URLSearchParams() } }; + const response = await GET(request as any); + const payload = await response.json(); + + expect(response.status).toBe(400); + expect(payload.error).toMatch(/Missing token/); + }); + + it('returns 401 when token is invalid or user not found', async () => { + prismaMock.user.findFirst.mockResolvedValueOnce(null); + + const { GET } = await import('@/app/api/auth/token/login/route'); + const request = { nextUrl: { searchParams: new URLSearchParams('token=rmab_invalid') } }; + const response = await GET(request as any); + const payload = await response.json(); + + expect(response.status).toBe(401); + expect(payload.error).toMatch(/Invalid token/); + }); +});