feat: add unit tests and CI configuration

This commit is contained in:
Usman Baig
2026-03-01 00:11:54 +01:00
parent bce56fa64d
commit b5f83ce582
12 changed files with 2798 additions and 3 deletions

View 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.')
})
})

View 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()
})
})

View 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()
})
})