120 lines
4.7 KiB
TypeScript
120 lines
4.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { NextFunction, Request, Response } from 'express'
|
|
|
|
vi.mock('jsonwebtoken', () => ({
|
|
default: { verify: vi.fn() },
|
|
}))
|
|
|
|
vi.mock('../lib/prisma', () => ({
|
|
prisma: {
|
|
employee: { findUnique: vi.fn() },
|
|
},
|
|
}))
|
|
|
|
import jwt from 'jsonwebtoken'
|
|
import { prisma } from '../lib/prisma'
|
|
import { requireCompanyAuth, requireCompanyDocumentAuth } from './requireCompanyAuth'
|
|
|
|
function responseStub() {
|
|
const res = { status: vi.fn(), json: vi.fn() }
|
|
res.status.mockReturnValue(res)
|
|
res.json.mockReturnValue(res)
|
|
return res as unknown as Response & typeof res
|
|
}
|
|
|
|
describe('requireCompanyAuth middleware', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
process.env.JWT_SECRET = 'test-secret'
|
|
})
|
|
|
|
it('rejects missing bearer tokens', async () => {
|
|
const req = { headers: {} } as Request
|
|
const res = responseStub()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireCompanyAuth(req, res, next)
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401)
|
|
expect(res.json).toHaveBeenCalledWith({ error: 'unauthenticated', message: 'Authentication required', statusCode: 401 })
|
|
expect(prisma.employee.findUnique).not.toHaveBeenCalled()
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects invalid tokens', async () => {
|
|
vi.mocked(jwt.verify).mockImplementation(() => { throw new Error('bad token') })
|
|
const req = { headers: { authorization: 'Bearer bad' } } as Request
|
|
const res = responseStub()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireCompanyAuth(req, res, next)
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401)
|
|
expect(res.json).toHaveBeenCalledWith({ error: 'invalid_token', message: 'Invalid or expired session token', statusCode: 401 })
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects non-employee token types', async () => {
|
|
vi.mocked(jwt.verify).mockReturnValue({ sub: 'renter_1', type: 'renter' } as any)
|
|
const req = { headers: { authorization: 'Bearer renter-token' } } as Request
|
|
const res = responseStub()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireCompanyAuth(req, res, next)
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401)
|
|
expect(res.json).toHaveBeenCalledWith({ error: 'invalid_token', message: 'Invalid or expired session token', statusCode: 401 })
|
|
expect(prisma.employee.findUnique).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects inactive or missing employees', async () => {
|
|
vi.mocked(jwt.verify).mockReturnValue({ sub: 'emp_1', type: 'employee' } as any)
|
|
vi.mocked(prisma.employee.findUnique).mockResolvedValue({ id: 'emp_1', isActive: false } as any)
|
|
const req = { headers: { authorization: 'Bearer employee-token' } } as Request
|
|
const res = responseStub()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireCompanyAuth(req, res, next)
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401)
|
|
expect(res.json).toHaveBeenCalledWith({ error: 'unauthenticated', message: 'Employee account not found or inactive', statusCode: 401 })
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('attaches employee and company context for active employees', async () => {
|
|
const employee = { id: 'emp_1', companyId: 'company_1', isActive: true, company: { id: 'company_1', name: 'Atlas' } }
|
|
vi.mocked(jwt.verify).mockReturnValue({ sub: 'emp_1', type: 'employee' } as any)
|
|
vi.mocked(prisma.employee.findUnique).mockResolvedValue(employee as any)
|
|
const req = { headers: { authorization: 'Bearer employee-token' } } as Request
|
|
const res = responseStub()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireCompanyAuth(req, res, next)
|
|
|
|
expect(prisma.employee.findUnique).toHaveBeenCalledWith({ where: { id: 'emp_1' }, include: { company: true } })
|
|
expect(req.employee).toEqual(employee)
|
|
expect(req.company).toEqual(employee.company)
|
|
expect(req.companyId).toBe('company_1')
|
|
expect(next).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('accepts employee_session cookies for document routes', async () => {
|
|
const employee = { id: 'emp_2', companyId: 'company_2', isActive: true, company: { id: 'company_2' } }
|
|
vi.mocked(jwt.verify).mockReturnValue({ sub: 'emp_2', type: 'employee' } as any)
|
|
vi.mocked(prisma.employee.findUnique).mockResolvedValue(employee as any)
|
|
const req = { headers: { cookie: 'employee_session=cookie-token' } } as Request
|
|
const res = responseStub()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireCompanyDocumentAuth(req, res, next)
|
|
|
|
expect(jwt.verify).toHaveBeenCalledWith('cookie-token', 'test-secret', {
|
|
algorithms: ['HS256'],
|
|
issuer: 'rentaldrivego-api',
|
|
audience: 'employee',
|
|
})
|
|
expect(req.companyId).toBe('company_2')
|
|
expect(next).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|