feat: add unit tests and CI configuration
This commit is contained in:
95
lib/utils/__tests__/errorHandler.test.ts
Normal file
95
lib/utils/__tests__/errorHandler.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import {
|
||||
getRequestIdFromError,
|
||||
formatErrorMessage,
|
||||
logErrorWithRequestId,
|
||||
getSupportMessage,
|
||||
} from '../errorHandler'
|
||||
import { setLastRequestId, clearLastRequestId } from '../requestId'
|
||||
|
||||
beforeEach(() => {
|
||||
clearLastRequestId()
|
||||
})
|
||||
|
||||
describe('getRequestIdFromError', () => {
|
||||
it('extracts request ID from error response body', () => {
|
||||
const errorData = { error: { request_id: 'REQ123_abc' } }
|
||||
expect(getRequestIdFromError(errorData)).toBe('REQ123_abc')
|
||||
})
|
||||
|
||||
it('falls back to last stored request ID when not in response', () => {
|
||||
setLastRequestId('REQfallback_xyz')
|
||||
expect(getRequestIdFromError({ error: {} })).toBe('REQfallback_xyz')
|
||||
})
|
||||
|
||||
it('falls back to last stored request ID when no error data', () => {
|
||||
setLastRequestId('REQfallback_xyz')
|
||||
expect(getRequestIdFromError()).toBe('REQfallback_xyz')
|
||||
})
|
||||
|
||||
it('returns null when no ID available anywhere', () => {
|
||||
expect(getRequestIdFromError()).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatErrorMessage', () => {
|
||||
it('returns plain message when no request ID available', () => {
|
||||
expect(formatErrorMessage('Something failed')).toBe('Something failed')
|
||||
})
|
||||
|
||||
it('appends request ID in development mode', () => {
|
||||
const original = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'development'
|
||||
setLastRequestId('REQ123_abc')
|
||||
|
||||
const msg = formatErrorMessage('Something failed')
|
||||
expect(msg).toContain('Something failed')
|
||||
expect(msg).toContain('REQ123_abc')
|
||||
|
||||
process.env.NODE_ENV = original
|
||||
})
|
||||
|
||||
it('appends request ID when showRequestId option is set', () => {
|
||||
setLastRequestId('REQ123_abc')
|
||||
const msg = formatErrorMessage('Something failed', undefined, { showRequestId: true })
|
||||
expect(msg).toContain('REQ123_abc')
|
||||
})
|
||||
})
|
||||
|
||||
describe('logErrorWithRequestId', () => {
|
||||
it('logs with request ID when available', () => {
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
setLastRequestId('REQ123_abc')
|
||||
|
||||
logErrorWithRequestId('TestContext', new Error('fail'))
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('REQ123_abc'),
|
||||
expect.any(Error)
|
||||
)
|
||||
spy.mockRestore()
|
||||
})
|
||||
|
||||
it('logs without request ID when not available', () => {
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
logErrorWithRequestId('TestContext', new Error('fail'))
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('[TestContext]', expect.any(Error))
|
||||
spy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSupportMessage', () => {
|
||||
it('includes request ID when available', () => {
|
||||
const errorData = { error: { request_id: 'REQ123_abc' } }
|
||||
const msg = getSupportMessage(errorData)
|
||||
expect(msg).toContain('REQ123_abc')
|
||||
expect(msg).toContain('contact support')
|
||||
})
|
||||
|
||||
it('returns generic message when no request ID', () => {
|
||||
const msg = getSupportMessage()
|
||||
expect(msg).toBe('If this persists, please contact support.')
|
||||
})
|
||||
})
|
||||
29
lib/utils/__tests__/logger.test.ts
Normal file
29
lib/utils/__tests__/logger.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
describe('logger', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('calls console.error in development', async () => {
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
const { logger } = await import('../logger')
|
||||
logger.error('test error')
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('test error')
|
||||
spy.mockRestore()
|
||||
})
|
||||
|
||||
it('calls console.warn in development', async () => {
|
||||
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
const { logger } = await import('../logger')
|
||||
logger.warn('test warning')
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('test warning')
|
||||
spy.mockRestore()
|
||||
})
|
||||
})
|
||||
61
lib/utils/__tests__/requestId.test.ts
Normal file
61
lib/utils/__tests__/requestId.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import {
|
||||
generateRequestId,
|
||||
getRequestIdHeader,
|
||||
setLastRequestId,
|
||||
getLastRequestId,
|
||||
clearLastRequestId,
|
||||
} from '../requestId'
|
||||
|
||||
describe('generateRequestId', () => {
|
||||
it('returns a string starting with REQ', () => {
|
||||
const id = generateRequestId()
|
||||
expect(id).toMatch(/^REQ/)
|
||||
})
|
||||
|
||||
it('contains a timestamp and random segment separated by underscore', () => {
|
||||
const id = generateRequestId()
|
||||
const parts = id.replace('REQ', '').split('_')
|
||||
expect(parts).toHaveLength(2)
|
||||
expect(parts[0].length).toBeGreaterThan(0)
|
||||
expect(parts[1].length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('generates unique IDs across calls', () => {
|
||||
const ids = new Set(Array.from({ length: 100 }, () => generateRequestId()))
|
||||
expect(ids.size).toBe(100)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRequestIdHeader', () => {
|
||||
it('returns X-Request-ID', () => {
|
||||
expect(getRequestIdHeader()).toBe('X-Request-ID')
|
||||
})
|
||||
})
|
||||
|
||||
describe('lastRequestId storage', () => {
|
||||
beforeEach(() => {
|
||||
clearLastRequestId()
|
||||
})
|
||||
|
||||
it('returns null when no ID has been set', () => {
|
||||
expect(getLastRequestId()).toBeNull()
|
||||
})
|
||||
|
||||
it('stores and retrieves a request ID', () => {
|
||||
setLastRequestId('REQ123_abc')
|
||||
expect(getLastRequestId()).toBe('REQ123_abc')
|
||||
})
|
||||
|
||||
it('overwrites previous ID on set', () => {
|
||||
setLastRequestId('first')
|
||||
setLastRequestId('second')
|
||||
expect(getLastRequestId()).toBe('second')
|
||||
})
|
||||
|
||||
it('clears the stored ID', () => {
|
||||
setLastRequestId('REQ123_abc')
|
||||
clearLastRequestId()
|
||||
expect(getLastRequestId()).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user