implement driver license date validation
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('../../lib/prisma', () => ({ prisma: {} }))
|
||||
vi.mock('../../lib/redis', () => ({
|
||||
redis: { on: vi.fn(), get: vi.fn(), set: vi.fn(), del: vi.fn(), quit: vi.fn() },
|
||||
}))
|
||||
|
||||
import request from 'supertest'
|
||||
import { createApp } from '../../app'
|
||||
|
||||
const app = createApp()
|
||||
|
||||
/** Return today's date as YYYY-MM-DD */
|
||||
function today(): string {
|
||||
return new Date().toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
/** Return a date offset by `days` from today, as YYYY-MM-DD */
|
||||
function offsetDate(days: number): string {
|
||||
const d = new Date()
|
||||
d.setDate(d.getDate() + days)
|
||||
return d.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
describe('License Validation API — POST /api/v1/licenses/validate', () => {
|
||||
// ─── Success scenarios ──────────────────────────────────────────────
|
||||
|
||||
it('accepts a license expiring in 365 days (valid)', async () => {
|
||||
const future = offsetDate(365)
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: future })
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.data).toMatchObject({
|
||||
valid: true,
|
||||
message: 'License verified successfully',
|
||||
})
|
||||
expect(res.body.data.days_remaining).toBeGreaterThanOrEqual(365)
|
||||
expect(res.body.data.next_reminder).toBeDefined()
|
||||
})
|
||||
|
||||
it('accepts a license expiring in exactly 30 days', async () => {
|
||||
const future = offsetDate(30)
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: future })
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.data.valid).toBe(true)
|
||||
expect(res.body.data.days_remaining).toBe(30)
|
||||
})
|
||||
|
||||
// ─── Rejection scenarios ────────────────────────────────────────────
|
||||
|
||||
it('rejects a license expiring in 15 days with 422', async () => {
|
||||
const nearFuture = offsetDate(15)
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: nearFuture })
|
||||
|
||||
expect(res.status).toBe(422)
|
||||
expect(res.body.error).toBe('LICENSE_EXPIRING_SOON')
|
||||
expect(res.body.message).toContain('30 days')
|
||||
expect(res.body.details).toBeDefined()
|
||||
expect(res.body.details.days_remaining).toBe(15)
|
||||
expect(res.body.details.minimum_required).toBe(30)
|
||||
})
|
||||
|
||||
it('rejects an expired license with 422', async () => {
|
||||
const past = offsetDate(-5)
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: past })
|
||||
|
||||
expect(res.status).toBe(422)
|
||||
expect(res.body.error).toBe('LICENSE_EXPIRED')
|
||||
expect(res.body.message).toContain('expired')
|
||||
})
|
||||
|
||||
it('rejects an invalid date format with 400', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: 'not-a-date' })
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
expect(res.body.error).toBe('LICENSE_INVALID_FORMAT')
|
||||
expect(res.body.message).toContain('Unable to parse')
|
||||
})
|
||||
|
||||
it('rejects an empty expiration_date with 400', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: '' })
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
expect(res.body.error).toBe('validation_error')
|
||||
})
|
||||
|
||||
it('rejects a date too far in the future with 400', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: '2055-01-01' })
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
expect(res.body.error).toBe('LICENSE_FUTURE_DATE')
|
||||
})
|
||||
|
||||
// ─── Multiple date formats ──────────────────────────────────────────
|
||||
|
||||
it('parses MM/DD/YYYY format', async () => {
|
||||
const future = new Date()
|
||||
future.setFullYear(future.getFullYear() + 1)
|
||||
const mm = String(future.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(future.getDate()).padStart(2, '0')
|
||||
const yyyy = future.getFullYear()
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: `${mm}/${dd}/${yyyy}` })
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.data.valid).toBe(true)
|
||||
})
|
||||
|
||||
it('parses DD-MMM-YYYY format', async () => {
|
||||
const future = new Date()
|
||||
future.setFullYear(future.getFullYear() + 1)
|
||||
const dd = String(future.getDate()).padStart(2, '0')
|
||||
const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
|
||||
const mmm = months[future.getMonth()]
|
||||
const yyyy = future.getFullYear()
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/v1/licenses/validate')
|
||||
.send({ expiration_date: `${dd}-${mmm}-${yyyy}` })
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.data.valid).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user