UNPKG

@dbs-portal/tool-mock

Version:

API mocking toolkit using MSW for DBS Portal development workflows

665 lines (577 loc) 16.8 kB
# Usage Examples - @dbs-portal/tool-mock Real-world examples demonstrating common usage patterns and integration scenarios. ## Basic Setup Examples ### Quick Start - Auto Setup ```typescript // main.tsx import { autoSetupMocks } from '@dbs-portal/tool-mock' // Automatically setup MSW based on environment await autoSetupMocks() // Start your React app ReactDOM.createRoot(document.getElementById('root')!).render(<App />) ``` ### Manual Setup with Custom Configuration ```typescript // main.tsx import { setupMocks, createCrudHandlers } from '@dbs-portal/tool-mock' await setupMocks({ config: { enabled: process.env.NODE_ENV === 'development', mode: 'development', logging: true, delay: [100, 300], errorSimulation: { networkErrorRate: 0.01, // 1% network errors serverErrorRate: 0.005, // 0.5% server errors }, }, handlers: [ ...createCrudHandlers({ basePath: '/api/users', dataFactory: userFactory, }), ], }) ``` ## CRUD Operations ### Complete User Management Example ```typescript // types/user.ts export interface User { id: string email: string firstName: string lastName: string department: string role: string isActive: boolean createdAt: string updatedAt: string } export interface CreateUserDto { email: string firstName: string lastName: string department: string role: string } export interface UpdateUserDto extends Partial<CreateUserDto> {} ``` ```typescript // mocks/user-factory.ts import { createDataFactory, generateId } from '@dbs-portal/tool-mock' import type { User } from '../types/user' export const userFactory = createDataFactory<User>((overrides = {}) => ({ id: generateId(), email: `user${Math.floor(Math.random() * 1000)}@example.com`, firstName: ['John', 'Jane', 'Mike', 'Sarah', 'David'][Math.floor(Math.random() * 5)], lastName: ['Doe', 'Smith', 'Johnson', 'Brown', 'Davis'][Math.floor(Math.random() * 5)], department: ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'][Math.floor(Math.random() * 5)], role: ['Developer', 'Manager', 'Analyst', 'Coordinator'][Math.floor(Math.random() * 4)], isActive: Math.random() > 0.2, // 80% active createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(), updatedAt: new Date().toISOString(), ...overrides, })) ``` ```typescript // mocks/user-handlers.ts import { createCrudHandlers } from '@dbs-portal/tool-mock' import { userFactory } from './user-factory' export const userHandlers = createCrudHandlers({ basePath: '/api/users', dataFactory: userFactory, initialData: [ userFactory({ id: '1', email: 'admin@example.com', firstName: 'Admin', lastName: 'User', role: 'Admin', department: 'IT', }), userFactory({ id: '2', email: 'manager@example.com', firstName: 'Manager', lastName: 'User', role: 'Manager', department: 'Engineering', }), ...Array.from({ length: 48 }, () => userFactory()), // 50 total users ], pagination: { defaultPageSize: 10, maxPageSize: 100, }, validate: (data) => { const errors: string[] = [] if (!data.email) { errors.push('Email is required') } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { errors.push('Email format is invalid') } if (!data.firstName) errors.push('First name is required') if (!data.lastName) errors.push('Last name is required') if (!data.department) errors.push('Department is required') if (!data.role) errors.push('Role is required') return errors.length > 0 ? errors : null }, }) ``` ### Custom Endpoints ```typescript // mocks/custom-handlers.ts import { http, HttpResponse } from 'msw' import { mockResponseBuilder } from '@dbs-portal/tool-mock' import { userFactory } from './user-factory' export const customUserHandlers = [ // Bulk operations http.post('/api/users/bulk', async ({ request }) => { const { userIds, action } = await request.json() return mockResponseBuilder() .data({ processedCount: userIds.length, action, timestamp: new Date().toISOString(), }) .status(200) .delay([200, 500]) .build() }), // User statistics http.get('/api/users/stats', () => { return HttpResponse.json({ success: true, data: { total: 50, active: 40, inactive: 10, byDepartment: { Engineering: 15, Marketing: 10, Sales: 12, HR: 8, Finance: 5, }, byRole: { Developer: 20, Manager: 10, Analyst: 12, Coordinator: 8, }, }, }) }), // User search http.get('/api/users/search', ({ request }) => { const url = new URL(request.url) const query = url.searchParams.get('q') || '' const department = url.searchParams.get('department') // Simulate search results const results = Array.from({ length: Math.floor(Math.random() * 10) + 1 }, () => userFactory({ firstName: query ? `${query}Match` : undefined, department: department || undefined, }) ) return HttpResponse.json({ success: true, data: results, meta: { query, total: results.length, }, }) }), // User avatar upload http.post('/api/users/:id/avatar', ({ params }) => { return mockResponseBuilder() .data({ userId: params.id, avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${params.id}`, uploadedAt: new Date().toISOString(), }) .status(201) .delay([1000, 2000]) // Simulate file upload time .build() }), ] ``` ## Authentication Examples ### Complete Auth Flow ```typescript // mocks/auth-handlers.ts import { createAuthHandlers } from '@dbs-portal/tool-mock' export const authHandlers = createAuthHandlers('/api/auth', { users: [ { id: '1', email: 'admin@example.com', password: 'admin123', firstName: 'Admin', lastName: 'User', roles: ['admin'], permissions: [ 'users:read', 'users:write', 'users:delete', 'roles:read', 'roles:write', 'settings:read', 'settings:write', ], }, { id: '2', email: 'manager@example.com', password: 'manager123', firstName: 'Manager', lastName: 'User', roles: ['manager'], permissions: [ 'users:read', 'users:write', 'roles:read', ], }, { id: '3', email: 'user@example.com', password: 'user123', firstName: 'Regular', lastName: 'User', roles: ['user'], permissions: [ 'users:read', ], }, ], tokenExpiry: '1h', refreshTokenExpiry: '7d', enableRegistration: true, enablePasswordReset: true, }) ``` ### Protected Routes Example ```typescript // mocks/protected-handlers.ts import { withMockAuth, createCrudHandlers } from '@dbs-portal/tool-mock' import { userFactory } from './user-factory' // Admin-only user management const adminUserHandlers = withMockAuth( createCrudHandlers({ basePath: '/api/admin/users', dataFactory: userFactory, }), { requireAuth: true, requiredRoles: ['admin'], requiredPermissions: ['users:write'], } ) // Manager-level user viewing const managerUserHandlers = withMockAuth( [ http.get('/api/manager/users', () => { return HttpResponse.json({ success: true, data: Array.from({ length: 10 }, () => userFactory()), }) }), ], { requireAuth: true, requiredRoles: ['manager', 'admin'], requiredPermissions: ['users:read'], } ) // User profile (own data only) const profileHandlers = withMockAuth( [ http.get('/api/profile', ({ request }) => { // Extract user from auth token (mocked) const authHeader = request.headers.get('Authorization') const userId = extractUserIdFromToken(authHeader) // Mock implementation return HttpResponse.json({ success: true, data: userFactory({ id: userId }), }) }), ], { requireAuth: true, // No role/permission requirements - any authenticated user } ) ``` ## Error Simulation Examples ### Network Conditions ```typescript // mocks/error-simulation.ts import { setupMocks, createCrudHandlers } from '@dbs-portal/tool-mock' await setupMocks({ config: { errorSimulation: { networkErrorRate: 0.05, // 5% network errors serverErrorRate: 0.02, // 2% server errors timeoutErrorRate: 0.01, // 1% timeout errors customErrors: [ { pattern: '/api/users', method: 'POST', error: { code: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests', status: 429, }, probability: 0.1, // 10% chance for user creation }, { pattern: /\/api\/users\/\d+/, method: 'DELETE', error: () => ({ code: 'CANNOT_DELETE_ADMIN', message: 'Cannot delete admin user', status: 403, }), probability: 0.3, // 30% chance for user deletion }, ], }, }, handlers: [ ...createCrudHandlers({ basePath: '/api/users', dataFactory: userFactory, }), ], }) ``` ### Specific Error Scenarios ```typescript // mocks/error-handlers.ts import { http, HttpResponse } from 'msw' import { createErrorResponse } from '@dbs-portal/tool-mock' export const errorHandlers = [ // Validation errors http.post('/api/users', async ({ request }) => { const data = await request.json() // Simulate validation errors if (!data.email || !data.firstName) { return createErrorResponse({ code: 'VALIDATION_ERROR', message: 'Validation failed', details: { email: !data.email ? ['Email is required'] : [], firstName: !data.firstName ? ['First name is required'] : [], }, status: 400, }) } // Simulate duplicate email if (data.email === 'duplicate@example.com') { return createErrorResponse({ code: 'DUPLICATE_EMAIL', message: 'Email already exists', status: 409, }) } // Success case return HttpResponse.json({ success: true, data: userFactory(data), }) }), // Server errors http.get('/api/users/problematic-endpoint', () => { return createErrorResponse({ code: 'INTERNAL_SERVER_ERROR', message: 'Something went wrong on our end', status: 500, }) }), // Timeout simulation http.get('/api/users/slow-endpoint', () => { return new Promise((resolve) => { setTimeout(() => { resolve(HttpResponse.json({ success: true, data: [], })) }, 10000) // 10 second delay }) }), ] ``` ## File Upload Examples ### File Upload Mocking ```typescript // mocks/file-handlers.ts import { http, HttpResponse } from 'msw' import { mockResponseBuilder, generateId } from '@dbs-portal/tool-mock' export const fileHandlers = [ // Single file upload http.post('/api/files/upload', async ({ request }) => { const formData = await request.formData() const file = formData.get('file') as File if (!file) { return mockResponseBuilder() .error({ code: 'NO_FILE_PROVIDED', message: 'No file provided', }) .status(400) .build() } // Simulate file validation const maxSize = 10 * 1024 * 1024 // 10MB if (file.size > maxSize) { return mockResponseBuilder() .error({ code: 'FILE_TOO_LARGE', message: 'File size exceeds 10MB limit', }) .status(413) .build() } const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'] if (!allowedTypes.includes(file.type)) { return mockResponseBuilder() .error({ code: 'INVALID_FILE_TYPE', message: 'File type not allowed', }) .status(415) .build() } // Simulate upload progress (in real app, this would be handled differently) return mockResponseBuilder() .data({ id: generateId(), filename: file.name, originalName: file.name, mimeType: file.type, size: file.size, url: `https://example.com/files/${generateId()}`, uploadedAt: new Date().toISOString(), }) .status(201) .delay([1000, 3000]) // Simulate upload time .build() }), // Multiple file upload http.post('/api/files/upload/multiple', async ({ request }) => { const formData = await request.formData() const files = formData.getAll('files') as File[] const uploadedFiles = files.map((file) => ({ id: generateId(), filename: file.name, originalName: file.name, mimeType: file.type, size: file.size, url: `https://example.com/files/${generateId()}`, uploadedAt: new Date().toISOString(), })) return mockResponseBuilder() .data({ files: uploadedFiles, totalCount: uploadedFiles.length, totalSize: uploadedFiles.reduce((sum, file) => sum + file.size, 0), }) .status(201) .delay([2000, 5000]) // Longer delay for multiple files .build() }), // File download http.get('/api/files/:id/download', ({ params }) => { // Simulate file content const content = new Blob(['Mock file content'], { type: 'text/plain' }) return new HttpResponse(content, { status: 200, headers: { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="file-${params.id}.txt"`, }, }) }), ] ``` ## Testing Examples ### Component Testing with MSW ```typescript // __tests__/UserList.test.tsx import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { mockApiCall, userFactory } from '@dbs-portal/tool-mock' import { UserList } from '../UserList' const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }) return ({ children }: { children: React.ReactNode }) => ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ) } describe('UserList', () => { test('should display users', async () => { const mockUsers = [ userFactory({ id: '1', firstName: 'John', lastName: 'Doe' }), userFactory({ id: '2', firstName: 'Jane', lastName: 'Smith' }), ] mockApiCall('GET', '/api/users', { success: true, data: mockUsers, meta: { total: 2, page: 1, pageSize: 10 }, }) render(<UserList />, { wrapper: createWrapper() }) expect(await screen.findByText('John Doe')).toBeInTheDocument() expect(await screen.findByText('Jane Smith')).toBeInTheDocument() }) test('should handle loading state', async () => { mockApiCall('GET', '/api/users', { delay: 1000, success: true, data: [], }) render(<UserList />, { wrapper: createWrapper() }) expect(screen.getByText('Loading...')).toBeInTheDocument() }) test('should handle error state', async () => { mockApiCall('GET', '/api/users', { status: 500, success: false, error: { message: 'Internal server error' }, }) render(<UserList />, { wrapper: createWrapper() }) expect(await screen.findByText(/error/i)).toBeInTheDocument() }) test('should handle user creation', async () => { const user = userEvent.setup() const newUser = userFactory({ firstName: 'New', lastName: 'User' }) // Mock the create endpoint mockApiCall('POST', '/api/users', { success: true, data: newUser, }) // Mock the updated list mockApiCall('GET', '/api/users', { success: true, data: [newUser], }) render(<UserList />, { wrapper: createWrapper() }) const createButton = screen.getByRole('button', { name: /create user/i }) await user.click(createButton) // Fill form and submit await user.type(screen.getByLabelText(/first name/i), 'New') await user.type(screen.getByLabelText(/last name/i), 'User') await user.click(screen.getByRole('button', { name: /save/i })) expect(await screen.findByText('New User')).toBeInTheDocument() }) })