5428eeccc1
49 tests for all 10 Zod schemas in validate.ts, 26 tests for auth (hashPassword, verifyPassword, createSession, getSession, getCurrentUser, deleteSession), 11 for storage (magic-byte validation, saveImage, deleteImageFile), 9 for email (sendMail scenarios), and 6 for rate limiting logic.
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import '../../../test/__mocks__/prisma'
|
|
|
|
import { checkRateLimit, recordAttempt } from '@/lib/rate-limit'
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('checkRateLimit', () => {
|
|
it('returns not limited when attempts < 10', async () => {
|
|
vi.mocked(prisma.loginAttempt.count).mockResolvedValue(5)
|
|
const result = await checkRateLimit('1.2.3.4')
|
|
expect(result.limited).toBe(false)
|
|
expect(result.remaining).toBe(5)
|
|
})
|
|
|
|
it('returns limited when attempts >= 10', async () => {
|
|
vi.mocked(prisma.loginAttempt.count).mockResolvedValue(10)
|
|
const result = await checkRateLimit('1.2.3.4')
|
|
expect(result.limited).toBe(true)
|
|
expect(result.remaining).toBe(0)
|
|
})
|
|
|
|
it('returns limited when attempts > 10', async () => {
|
|
vi.mocked(prisma.loginAttempt.count).mockResolvedValue(15)
|
|
const result = await checkRateLimit('1.2.3.4')
|
|
expect(result.limited).toBe(true)
|
|
})
|
|
|
|
it('returns remaining = 1 when attempts = 9', async () => {
|
|
vi.mocked(prisma.loginAttempt.count).mockResolvedValue(9)
|
|
const result = await checkRateLimit('1.2.3.4')
|
|
expect(result.limited).toBe(false)
|
|
expect(result.remaining).toBe(1)
|
|
})
|
|
|
|
it('queries with a windowStart within the last 15 minutes', async () => {
|
|
vi.mocked(prisma.loginAttempt.count).mockResolvedValue(0)
|
|
const before = new Date(Date.now() - 15 * 60 * 1000 - 100)
|
|
|
|
await checkRateLimit('1.2.3.4')
|
|
|
|
const callArg = vi.mocked(prisma.loginAttempt.count).mock.calls[0][0]
|
|
const windowStart = callArg?.where?.createdAt?.gte as Date
|
|
expect(windowStart.getTime()).toBeGreaterThan(before.getTime())
|
|
})
|
|
})
|
|
|
|
describe('recordAttempt', () => {
|
|
it('creates a login attempt record', async () => {
|
|
vi.mocked(prisma.loginAttempt.create).mockResolvedValue({ id: '1', key: '1.2.3.4', createdAt: new Date() })
|
|
vi.mocked(prisma.loginAttempt.deleteMany).mockResolvedValue({ count: 0 })
|
|
|
|
await recordAttempt('1.2.3.4')
|
|
|
|
expect(prisma.loginAttempt.create).toHaveBeenCalledWith({ data: { key: '1.2.3.4' } })
|
|
})
|
|
|
|
it('cleans up old records after creating', async () => {
|
|
vi.mocked(prisma.loginAttempt.create).mockResolvedValue({ id: '1', key: '1.2.3.4', createdAt: new Date() })
|
|
vi.mocked(prisma.loginAttempt.deleteMany).mockResolvedValue({ count: 0 })
|
|
|
|
await recordAttempt('1.2.3.4')
|
|
|
|
expect(prisma.loginAttempt.deleteMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({ where: expect.objectContaining({ createdAt: expect.anything() }) })
|
|
)
|
|
})
|
|
})
|