Exemples Mock Service Worker

Exemples complets de MSW (Mock Service Worker) couvrant le mocking d'API, GraphQL, le mocking de WebSocket et les patterns avancés de traitement des requêtes

Key Facts

Category
Testing & Development
Items
3
Format Families
sample

Sample Overview

Exemples complets de MSW (Mock Service Worker) couvrant le mocking d'API, GraphQL, le mocking de WebSocket et les patterns avancés de traitement des requêtes This sample set belongs to Testing & Development and can be used to test related workflows inside Elysia Tools.

💻 Basics du Mocking REST API javascript

🟢 simple ⭐⭐

Mocking fondamental de REST API couvrant les opérations GET, POST, PUT, DELETE et le traitement des réponses

⏱️ 25 min 🏷️ msw, api mocking, rest, testing
Prerequisites: JavaScript, REST API, HTTP methods, Testing basics
// Mock Service Worker - REST API Basics
// File: src/mocks/handlers.js

import { rest } from 'msw'

// 1. Basic GET request handler
export const handlers = [
  // Get user by ID
  rest.get('/api/users/:id', (req, res, ctx) => {
    const { id } = req.params

    // Mock user data
    const users = {
      '1': { id: 1, name: 'John Doe', email: '[email protected]', role: 'user' },
      '2': { id: 2, name: 'Jane Smith', email: '[email protected]', role: 'admin' },
      '3': { id: 3, name: 'Bob Johnson', email: '[email protected]', role: 'user' }
    }

    const user = users[id]

    if (user) {
      return res(
        ctx.status(200),
        ctx.json(user)
      )
    }

    return res(
      ctx.status(404),
      ctx.json({ error: 'User not found' })
    )
  }),

  // Get all users with query parameters
  rest.get('/api/users', (req, res, ctx) => {
    const page = parseInt(req.url.searchParams.get('page') || '1')
    const limit = parseInt(req.url.searchParams.get('limit') || '10')
    const role = req.url.searchParams.get('role')

    // Mock pagination
    const allUsers = [
      { id: 1, name: 'John Doe', email: '[email protected]', role: 'user' },
      { id: 2, name: 'Jane Smith', email: '[email protected]', role: 'admin' },
      { id: 3, name: 'Bob Johnson', email: '[email protected]', role: 'user' },
      { id: 4, name: 'Alice Brown', email: '[email protected]', role: 'user' },
      { id: 5, name: 'Charlie Wilson', email: '[email protected]', role: 'moderator' }
    ]

    let filteredUsers = allUsers
    if (role) {
      filteredUsers = allUsers.filter(user => user.role === role)
    }

    const startIndex = (page - 1) * limit
    const endIndex = startIndex + limit
    const paginatedUsers = filteredUsers.slice(startIndex, endIndex)

    return res(
      ctx.status(200),
      ctx.json({
        users: paginatedUsers,
        pagination: {
          page,
          limit,
          total: filteredUsers.length,
          totalPages: Math.ceil(filteredUsers.length / limit)
        }
      })
    )
  }),

  // POST - Create new user
  rest.post('/api/users', async (req, res, ctx) => {
    const userData = await req.json()

    // Validation
    if (!userData.name || !userData.email) {
      return res(
        ctx.status(400),
        ctx.json({ error: 'Name and email are required' })
      )
    }

    // Check if email already exists
    const existingEmails = ['[email protected]', '[email protected]', '[email protected]']
    if (existingEmails.includes(userData.email)) {
      return res(
        ctx.status(409),
        ctx.json({ error: 'Email already exists' })
      )
    }

    // Mock user creation
    const newUser = {
      id: Date.now(),
      ...userData,
      role: userData.role || 'user',
      createdAt: new Date().toISOString()
    }

    return res(
      ctx.status(201),
      ctx.json(newUser)
    )
  }),

  // PUT - Update user
  rest.put('/api/users/:id', async (req, res, ctx) => {
    const { id } = req.params
    const updateData = await req.json()

    // Mock existing user
    const existingUser = {
      id: parseInt(id),
      name: 'John Doe',
      email: '[email protected]',
      role: 'user',
      updatedAt: new Date().toISOString()
    }

    // Validate update data
    if (updateData.email && !updateData.email.includes('@')) {
      return res(
        ctx.status(400),
        ctx.json({ error: 'Invalid email format' })
      )
    }

    const updatedUser = {
      ...existingUser,
      ...updateData,
      updatedAt: new Date().toISOString()
    }

    return res(
      ctx.status(200),
      ctx.json(updatedUser)
    )
  }),

  // DELETE - Remove user
  rest.delete('/api/users/:id', (req, res, ctx) => {
    const { id } = req.params

    // Simulate user deletion
    if (id === '1') {
      return res(
        ctx.status(204)
      )
    }

    return res(
      ctx.status(404),
      ctx.json({ error: 'User not found' })
    )
  }),

  // File upload handler
  rest.post('/api/upload', async (req, res, ctx) => {
    const contentType = req.headers.get('content-type')

    if (!contentType || !contentType.includes('multipart/form-data')) {
      return res(
        ctx.status(400),
        ctx.json({ error: 'File upload required' })
      )
    }

    // Mock file processing
    return res(
      ctx.status(200),
      ctx.json({
        message: 'File uploaded successfully',
        filename: 'example.jpg',
        size: 1024000,
        url: 'https://example.com/files/example.jpg',
        uploadedAt: new Date().toISOString()
      })
    )
  }),

  // Error simulation handler
  rest.get('/api/error-test', (req, res, ctx) => {
    const errorType = req.url.searchParams.get('type') || 'server'

    switch (errorType) {
      case 'server':
        return res(
          ctx.status(500),
          ctx.json({ error: 'Internal server error', code: 'INTERNAL_ERROR' })
        )
      case 'network':
        return res.networkError('Network connection failed')
      case 'timeout':
        return res(ctx.delay(10000), ctx.status(200), ctx.json({}))
      case 'unauthorized':
        return res(
          ctx.status(401),
          ctx.json({ error: 'Unauthorized', message: 'Please login first' })
        )
      default:
        return res(
          ctx.status(404),
          ctx.json({ error: 'Endpoint not found' })
        )
    }
  })
]

// File: src/mocks/server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'

// Create server for Node.js environment (testing)
export const server = setupServer(...handlers)

// File: src/mocks/browser.js
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

// Create worker for browser environment (development)
export const worker = setupWorker(...handlers)

// File: src/setupTests.js (for Jest)
import '@testing-library/jest-dom'
import { server } from './mocks/server'

// Establish API mocking before all tests
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))

// Reset any request handlers that we may add during the tests
afterEach(() => server.resetHandlers())

// Clean up after the tests are finished
afterAll(() => server.close())

// File: src/index.js (development setup)
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { worker } from './mocks/browser'

// Start Mock Service Worker in development
if (process.env.NODE_ENV === 'development') {
  worker.start({
    onUnhandledRequest: 'warn',
    serviceWorker: {
      url: '/mockServiceWorker.js'
    }
  }).then(() => {
    console.log('Mock Service Worker started successfully')
  }).catch(error => {
    console.error('Failed to start Mock Service Worker:', error)
  })
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

// Usage Example Component
// File: src/components/UserManager.js
import React, { useState, useEffect } from 'react'

function UserManager() {
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  useEffect(() => {
    fetchUsers()
  }, [])

  const fetchUsers = async () => {
    try {
      setLoading(true)
      setError(null)

      const response = await fetch('/api/users')

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }

      const data = await response.json()
      setUsers(data.users || [])
    } catch (error) {
      setError(error.message)
    } finally {
      setLoading(false)
    }
  }

  const createUser = async (userData) => {
    try {
      setLoading(true)

      const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
      })

      if (!response.ok) {
        const errorData = await response.json()
        throw new Error(errorData.error || 'Failed to create user')
      }

      const newUser = await response.json()
      setUsers(prev => [...prev, newUser])
      return newUser
    } catch (error) {
      setError(error.message)
      throw error
    } finally {
      setLoading(false)
    }
  }

  const updateUser = async (id, userData) => {
    try {
      setLoading(true)

      const response = await fetch(`/api/users/${id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
      })

      if (!response.ok) {
        const errorData = await response.json()
        throw new Error(errorData.error || 'Failed to update user')
      }

      const updatedUser = await response.json()
      setUsers(prev => prev.map(user =>
        user.id === parseInt(id) ? updatedUser : user
      ))
      return updatedUser
    } catch (error) {
      setError(error.message)
      throw error
    } finally {
      setLoading(false)
    }
  }

  const deleteUser = async (id) => {
    try {
      setLoading(true)

      const response = await fetch(`/api/users/${id}`, {
        method: 'DELETE'
      })

      if (!response.ok) {
        throw new Error('Failed to delete user')
      }

      setUsers(prev => prev.filter(user => user.id !== parseInt(id)))
    } catch (error) {
      setError(error.message)
      throw error
    } finally {
      setLoading(false)
    }
  }

  if (loading && users.length === 0) {
    return <div>Loading users...</div>
  }

  if (error) {
    return <div>Error: {error}</div>
  }

  return (
    <div>
      <h1>User Manager</h1>
      <button onClick={fetchUsers}>Refresh Users</button>

      {users.map(user => (
        <div key={user.id} style={{ marginBottom: '1rem', padding: '1rem', border: '1px solid #ccc' }}>
          <h3>{user.name}</h3>
          <p>Email: {user.email}</p>
          <p>Role: {user.role}</p>
          <button onClick={() => updateUser(user.id, { name: user.name + ' (Updated)' })}>
            Update
          </button>
          <button onClick={() => deleteUser(user.id)} style={{ marginLeft: '0.5rem' }}>
            Delete
          </button>
        </div>
      ))}
    </div>
  )
}

💻 Mocking GraphQL API javascript

🟡 intermediate ⭐⭐⭐⭐

Mocking complet de GraphQL incluant les queries, mutations, subscriptions et les relations de données complexes

⏱️ 35 min 🏷️ msw, graphql, mocking, subscriptions
Prerequisites: GraphQL, React Query/Apollo Client, JavaScript ES6+, Subscription concepts
// Mock Service Worker - GraphQL API Mocking
// File: src/mocks/graphqlHandlers.js

import { graphql, GraphQLWsLink } from 'msw'
import { graphql } from 'msw/node'
import { setupServer } from 'msw/node'

// Mock database
const mockDatabase = {
  users: [
    { id: '1', name: 'John Doe', email: '[email protected]', posts: ['1', '3'] },
    { id: '2', name: 'Jane Smith', email: '[email protected]', posts: ['2'] },
    { id: '3', name: 'Bob Johnson', email: '[email protected]', posts: [] }
  ],
  posts: [
    { id: '1', title: 'First Post', content: 'This is my first post', authorId: '1', tags: ['javascript', 'react'] },
    { id: '2', title: 'GraphQL Basics', content: 'Learning GraphQL', authorId: '2', tags: ['graphql', 'tutorial'] },
    { id: '3', title: 'Testing with MSW', content: 'How to mock APIs with MSW', authorId: '1', tags: ['testing', 'msw'] }
  ],
  comments: [
    { id: '1', postId: '1', authorId: '2', content: 'Great post!', createdAt: '2024-01-01T00:00:00Z' },
    { id: '2', postId: '1', authorId: '3', content: 'Thanks for sharing', createdAt: '2024-01-02T00:00:00Z' }
  ]
}

// GraphQL Handlers
export const graphqlHandlers = [
  // Query: Get all users
  graphql.query('GetUsers', (req, res, ctx) => {
    const { limit, offset, filter } = req.variables

    let users = mockDatabase.users

    // Apply filters
    if (filter?.name) {
      users = users.filter(user =>
        user.name.toLowerCase().includes(filter.name.toLowerCase())
      )
    }

    // Apply pagination
    const limitNum = limit || 10
    const offsetNum = offset || 0
    const paginatedUsers = users.slice(offsetNum, offsetNum + limitNum)

    return res(
      ctx.data({
        users: paginatedUsers.map(user => ({
          ...user,
          posts: mockDatabase.posts.filter(post => user.posts.includes(post.id))
        })),
        usersCount: users.length
      })
    )
  }),

  // Query: Get user by ID
  graphql.query('GetUser', (req, res, ctx) => {
    const { id } = req.variables

    const user = mockDatabase.users.find(u => u.id === id)

    if (!user) {
      return res(
        ctx.errors([
          {
            message: 'User not found',
            locations: [{ line: 1, column: 1 }],
            path: ['user']
          }
        ])
      )
    }

    const userPosts = mockDatabase.posts.filter(post => user.posts.includes(post.id))

    return res(
      ctx.data({
        user: {
          ...user,
          posts: userPosts.map(post => ({
            ...post,
            comments: mockDatabase.comments.filter(comment => comment.postId === post.id)
          }))
        }
      })
    )
  }),

  // Query: Get posts with optional filters
  graphql.query('GetPosts', (req, res, ctx) => {
    const { limit, offset, authorId, tags } = req.variables

    let posts = mockDatabase.posts

    // Filter by author
    if (authorId) {
      posts = posts.filter(post => post.authorId === authorId)
    }

    // Filter by tags
    if (tags && tags.length > 0) {
      posts = posts.filter(post =>
        tags.some(tag => post.tags.includes(tag))
      )
    }

    // Pagination
    const limitNum = limit || 10
    const offsetNum = offset || 0
    const paginatedPosts = posts.slice(offsetNum, offsetNum + limitNum)

    return res(
      ctx.data({
        posts: paginatedPosts.map(post => ({
          ...post,
          author: mockDatabase.users.find(user => user.id === post.authorId),
          comments: mockDatabase.comments.filter(comment => comment.postId === post.id)
        })),
        postsCount: posts.length
      })
    )
  }),

  // Query: Search posts
  graphql.query('SearchPosts', (req, res, ctx) => {
    const { query, limit = 10 } = req.variables

    if (!query || query.trim() === '') {
      return res(
        ctx.data({
          searchResults: [],
          searchCount: 0
        })
      )
    }

    const searchTerm = query.toLowerCase()
    const matchingPosts = mockDatabase.posts.filter(post =>
      post.title.toLowerCase().includes(searchTerm) ||
      post.content.toLowerCase().includes(searchTerm) ||
      post.tags.some(tag => tag.toLowerCase().includes(searchTerm))
    )

    const results = matchingPosts.slice(0, limit)

    return res(
      ctx.data({
        searchResults: results.map(post => ({
          ...post,
          author: mockDatabase.users.find(user => user.id === post.authorId),
          matchScore: calculateMatchScore(post, searchTerm)
        })),
        searchCount: matchingPosts.length
      })
    )
  }),

  // Mutation: Create user
  graphql.mutation('CreateUser', async (req, res, ctx) => {
    const { input } = req.variables
    const { name, email } = input

    // Validation
    if (!name || !email) {
      return res(
        ctx.errors([
          {
            message: 'Name and email are required',
            locations: [{ line: 1, column: 1 }],
            path: ['createUser']
          }
        ])
      )
    }

    // Check for duplicate email
    const existingUser = mockDatabase.users.find(u => u.email === email)
    if (existingUser) {
      return res(
        ctx.errors([
          {
            message: 'Email already exists',
            extensions: { code: 'DUPLICATE_EMAIL' }
          }
        ])
      )
    }

    // Create new user
    const newUser = {
      id: (mockDatabase.users.length + 1).toString(),
      name,
      email,
      posts: [],
      createdAt: new Date().toISOString()
    }

    mockDatabase.users.push(newUser)

    return res(
      ctx.delay(300), // Simulate network delay
      ctx.data({
        createUser: newUser
      })
    )
  }),

  // Mutation: Create post
  graphql.mutation('CreatePost', async (req, res, ctx) => {
    const { input } = req.variables
    const { title, content, authorId, tags = [] } = input

    // Validate author exists
    const author = mockDatabase.users.find(u => u.id === authorId)
    if (!author) {
      return res(
        ctx.errors([
          {
            message: 'Author not found',
            extensions: { code: 'AUTHOR_NOT_FOUND' }
          }
        ])
      )
    }

    // Create new post
    const newPost = {
      id: (mockDatabase.posts.length + 1).toString(),
      title,
      content,
      authorId,
      tags,
      createdAt: new Date().toISOString()
    }

    mockDatabase.posts.push(newPost)

    // Add post to author's posts
    author.posts.push(newPost.id)

    return res(
      ctx.delay(500), // Simulate network delay
      ctx.data({
        createPost: {
          ...newPost,
          author
        }
      })
    )
  }),

  // Mutation: Update post
  graphql.mutation('UpdatePost', async (req, res, ctx) => {
    const { id, input } = req.variables

    const postIndex = mockDatabase.posts.findIndex(p => p.id === id)
    if (postIndex === -1) {
      return res(
        ctx.errors([
          {
            message: 'Post not found',
            extensions: { code: 'POST_NOT_FOUND' }
          }
        ])
      )
    }

    // Update post
    const updatedPost = {
      ...mockDatabase.posts[postIndex],
      ...input,
      updatedAt: new Date().toISOString()
    }

    mockDatabase.posts[postIndex] = updatedPost

    const author = mockDatabase.users.find(u => u.id === updatedPost.authorId)

    return res(
      ctx.delay(200),
      ctx.data({
        updatePost: {
          ...updatedPost,
          author
        }
      })
    )
  }),

  // Mutation: Delete post
  graphql.mutation('DeletePost', async (req, res, ctx) => {
    const { id } = req.variables

    const postIndex = mockDatabase.posts.findIndex(p => p.id === id)
    if (postIndex === -1) {
      return res(
        ctx.errors([
          {
            message: 'Post not found',
            extensions: { code: 'POST_NOT_FOUND' }
          }
        ])
      )
    }

    // Remove post from database
    const deletedPost = mockDatabase.posts.splice(postIndex, 1)[0]

    // Remove post ID from author's posts
    const author = mockDatabase.users.find(u => u.id === deletedPost.authorId)
    if (author) {
      author.posts = author.posts.filter(postId => postId !== id)
    }

    // Remove related comments
    mockDatabase.comments = mockDatabase.comments.filter(
      comment => comment.postId !== id
    )

    return res(
      ctx.delay(100),
      ctx.data({
        deletePost: {
          id: deletedPost.id,
          success: true
        }
      })
    )
  }),

  // Mutation: Add comment
  graphql.mutation('AddComment', async (req, res, ctx) => {
    const { postId, content, authorId } = req.variables

    // Validate post and author exist
    const post = mockDatabase.posts.find(p => p.id === postId)
    const author = mockDatabase.users.find(u => u.id === authorId)

    if (!post) {
      return res(
        ctx.errors([
          {
            message: 'Post not found',
            extensions: { code: 'POST_NOT_FOUND' }
          }
        ])
      )
    }

    if (!author) {
      return res(
        ctx.errors([
          {
            message: 'Author not found',
            extensions: { code: 'AUTHOR_NOT_FOUND' }
          }
        ])
      )
    }

    // Create new comment
    const newComment = {
      id: (mockDatabase.comments.length + 1).toString(),
      postId,
      authorId,
      content,
      createdAt: new Date().toISOString()
    }

    mockDatabase.comments.push(newComment)

    return res(
      ctx.delay(150),
      ctx.data({
        addComment: {
          ...newComment,
          author
        }
      })
    )
  })
]

// Helper function for search score calculation
function calculateMatchScore(post, searchTerm) {
  let score = 0
  const lowerSearchTerm = searchTerm.toLowerCase()

  // Title matches get higher score
  if (post.title.toLowerCase().includes(lowerSearchTerm)) {
    score += 10
  }

  // Content matches
  if (post.content.toLowerCase().includes(lowerSearchTerm)) {
    score += 5
  }

  // Tag matches
  post.tags.forEach(tag => {
    if (tag.toLowerCase().includes(lowerSearchTerm)) {
      score += 3
    }
  })

  return score
}

// GraphQL Subscription handler (experimental)
export const subscriptionHandlers = [
  graphql.operation('PostCreated', (req, res, ctx) => {
    // This simulates a real-time subscription
    // In a real scenario, you'd use WebSocket connections
    return res(
      ctx.data({
        postCreated: {
          id: 'new-post-id',
          title: 'New Post Created',
          content: 'This is a newly created post',
          authorId: '1',
          createdAt: new Date().toISOString()
        }
      })
    )
  })
]

// Setup server for GraphQL
export const graphqlServer = setupServer(...graphqlHandlers)

// File: src/mocks/graphqlServer.js
import { setupServer } from 'msw/node'
import { graphqlHandlers } from './graphqlHandlers'

export const server = setupServer(...graphqlHandlers)

// File: src/mocks/graphqlBrowser.js
import { setupWorker } from 'msw/browser'
import { graphqlHandlers } from './graphqlHandlers'

export const worker = setupWorker(...graphqlHandlers)

// Usage Example - React Component
// File: src/components/PostManager.js
import React, { useState, useEffect } from 'react'
import { useQuery, useMutation, useSubscription } from '@apollo/client'
import { gql } from '@apollo/client'

const GET_POSTS = gql`
  query GetPosts($limit: Int, $offset: Int, $authorId: ID, $tags: [String]) {
    posts(limit: $limit, offset: $offset, authorId: $authorId, tags: $tags) {
      id
      title
      content
      createdAt
      author {
        id
        name
        email
      }
      comments {
        id
        content
        createdAt
        author {
          name
        }
      }
      tags
    }
    postsCount
  }
`

const CREATE_POST = gql`
  mutation CreatePost($input: CreatePostInput!) {
    createPost(input: $input) {
      id
      title
      content
      createdAt
      author {
        id
        name
        email
      }
      tags
    }
  }
`

const POST_CREATED_SUBSCRIPTION = gql`
  subscription PostCreated {
    postCreated {
      id
      title
      content
      author {
        name
      }
      createdAt
    }
  }
`

function PostManager() {
  const [limit] = useState(10)
  const [offset, setOffset] = useState(0)
  const [filter, setFilter] = useState({})

  const { data, loading, error, refetch } = useQuery(GET_POSTS, {
    variables: { limit, offset, ...filter }
  })

  const [createPost, { loading: creating }] = useMutation(CREATE_POST, {
    onCompleted: () => {
      refetch()
    }
  })

  // Subscription for real-time updates (if supported)
  const { data: subscriptionData } = useSubscription(POST_CREATED_SUBSCRIPTION)

  const handleCreatePost = async (postData) => {
    try {
      await createPost({
        variables: {
          input: {
            ...postData,
            authorId: '1' // Mock current user
          }
        }
      })
    } catch (error) {
      console.error('Error creating post:', error)
    }
  }

  if (loading) return <div>Loading posts...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <div>
      <h1>Post Manager</h1>

      {/* New post creation form */}
      <div style={{ marginBottom: '2rem', padding: '1rem', border: '1px solid #ccc' }}>
        <h2>Create New Post</h2>
        <form onSubmit={(e) => {
          e.preventDefault()
          const formData = new FormData(e.target)
          handleCreatePost({
            title: formData.get('title'),
            content: formData.get('content'),
            tags: formData.get('tags')?.split(',').map(tag => tag.trim())
          })
        }}>
          <div style={{ marginBottom: '0.5rem' }}>
            <input
              name="title"
              placeholder="Post title"
              required
              style={{ width: '100%', padding: '0.5rem' }}
            />
          </div>
          <div style={{ marginBottom: '0.5rem' }}>
            <textarea
              name="content"
              placeholder="Post content"
              required
              style={{ width: '100%', padding: '0.5rem', minHeight: '100px' }}
            />
          </div>
          <div style={{ marginBottom: '0.5rem' }}>
            <input
              name="tags"
              placeholder="Tags (comma-separated)"
              style={{ width: '100%', padding: '0.5rem' }}
            />
          </div>
          <button
            type="submit"
            disabled={creating}
            style={{ padding: '0.5rem 1rem', backgroundColor: '#0070f3', color: 'white' }}
          >
            {creating ? 'Creating...' : 'Create Post'}
          </button>
        </form>
      </div>

      {/* Filters */}
      <div style={{ marginBottom: '1rem' }}>
        <input
          placeholder="Filter by author ID"
          onChange={(e) => setFilter(prev => ({
            ...prev,
            authorId: e.target.value || undefined
          }))}
          style={{ marginRight: '0.5rem', padding: '0.25rem' }}
        />
        <input
          placeholder="Filter by tags (comma-separated)"
          onChange={(e) => setFilter(prev => ({
            ...prev,
            tags: e.target.value ? e.target.value.split(',').map(tag => tag.trim()) : undefined
          }))}
          style={{ padding: '0.25rem' }}
        />
      </div>

      {/* Posts list */}
      <div>
        <h2>Posts ({data?.postsCount || 0})</h2>
        {data?.posts?.map(post => (
          <div key={post.id} style={{ marginBottom: '1rem', padding: '1rem', border: '1px solid #ddd' }}>
            <h3>{post.title}</h3>
            <p>By: {post.author?.name}</p>
            <p>{post.content}</p>
            <div style={{ fontSize: '0.8rem', color: '#666' }}>
              Tags: {post.tags?.join(', ') || 'None'} |
              Created: {new Date(post.createdAt).toLocaleDateString()}
            </div>

            {/* Comments */}
            <div style={{ marginTop: '1rem' }}>
              <h4>Comments ({post.comments?.length || 0})</h4>
              {post.comments?.map(comment => (
                <div key={comment.id} style={{
                  marginLeft: '1rem',
                  padding: '0.5rem',
                  backgroundColor: '#f5f5f5',
                  borderRadius: '4px',
                  marginBottom: '0.5rem'
                }}>
                  <strong>{comment.author?.name}:</strong> {comment.content}
                  <div style={{ fontSize: '0.7rem', color: '#999' }}>
                    {new Date(comment.createdAt).toLocaleDateString()}
                  </div>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>

      {/* Pagination */}
      <div style={{ marginTop: '2rem', textAlign: 'center' }}>
        <button
          onClick={() => setOffset(prev => Math.max(0, prev - limit))}
          disabled={offset === 0}
          style={{ marginRight: '1rem', padding: '0.5rem 1rem' }}
        >
          Previous
        </button>
        <span>Page {Math.floor(offset / limit) + 1}</span>
        <button
          onClick={() => setOffset(prev => prev + limit)}
          disabled={!data?.posts || data.posts.length < limit}
          style={{ marginLeft: '1rem', padding: '0.5rem 1rem' }}
        >
          Next
        </button>
      </div>

      {/* Real-time updates */}
      {subscriptionData?.postCreated && (
        <div style={{
          position: 'fixed',
          bottom: '1rem',
          right: '1rem',
          padding: '1rem',
          backgroundColor: '#4CAF50',
          color: 'white',
          borderRadius: '4px'
        }}>
          <strong>New Post Alert!</strong><br/>
          "{subscriptionData.postCreated.title}" by {subscriptionData.postCreated.author?.name}
        </div>
      )}
    </div>
  )
}

💻 Patterns Avancés de MSW javascript

🔴 complex ⭐⭐⭐⭐⭐

Techniques avancées de MSW incluant les réponses dynamiques, le mocking de WebSocket, les flux d'authentification et les tests de performance

⏱️ 45 min 🏷️ msw, advanced, authentication, websockets, performance
Prerequisites: MSW basics, JavaScript advanced, WebSocket concepts, Authentication flows, Performance testing
// Advanced Mock Service Worker Patterns
// File: src/mocks/advancedHandlers.js

import { rest, graphql, WebSocketLink } from 'msw'
import { setupServer } from 'msw/node'

// 1. Dynamic Response Generation
export const dynamicHandlers = [
  // Dynamic user generator
  rest.get('/api/dynamic-users/:count', (req, res, ctx) => {
    const count = parseInt(req.params.count) || 10
    const seed = req.url.searchParams.get('seed') || 'default'

    // Seeded random number generator for consistent responses
    const random = createSeededRandom(seed)

    const users = Array.from({ length: count }, (_, index) => ({
      id: index + 1,
      name: generateRandomName(random),
      email: `user${index + 1}@${generateRandomDomain(random)}`,
      age: Math.floor(random() * 50) + 18,
      avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${seed}-${index}`,
      bio: generateRandomBio(random),
      skills: generateRandomSkills(random),
      createdAt: new Date(Date.now() - random() * 365 * 24 * 60 * 60 * 1000).toISOString()
    }))

    return res(
      ctx.json({
        users,
        generated: new Date().toISOString(),
        seed
      })
    )
  }),

  // Simulated real-time data updates
  rest.get('/api/stocks/:symbol', (req, res, ctx) => {
    const { symbol } = req.params
    const initialPrice = Math.random() * 1000 + 50

    // Generate realistic stock price fluctuations
    const generatePriceHistory = () => {
      let currentPrice = initialPrice
      return Array.from({ length: 100 }, (_, index) => {
        const change = (Math.random() - 0.5) * 10
        currentPrice = Math.max(1, currentPrice + change)
        return {
          timestamp: Date.now() - (99 - index) * 60000, // Last 100 minutes
          price: parseFloat(currentPrice.toFixed(2)),
          volume: Math.floor(Math.random() * 1000000) + 10000
        }
      })
    }

    return res(
      ctx.json({
        symbol,
        currentPrice: parseFloat(initialPrice.toFixed(2)),
        change: parseFloat(((Math.random() - 0.5) * 20).toFixed(2)),
        changePercent: parseFloat(((Math.random() - 0.5) * 5).toFixed(2)),
        volume: Math.floor(Math.random() * 10000000) + 100000,
        history: generatePriceHistory()
      })
    )
  })
]

// 2. Authentication Flow Handlers
export const authHandlers = [
  // Login endpoint
  rest.post('/api/auth/login', async (req, res, ctx) => {
    const { email, password } = await req.json()

    // Simulate authentication delay
    await ctx.delay(800)

    // Mock user database
    const users = [
      { email: '[email protected]', password: 'admin123', role: 'admin' },
      { email: '[email protected]', password: 'user123', role: 'user' },
      { email: '[email protected]', password: 'mod123', role: 'moderator' }
    ]

    const user = users.find(u => u.email === email && u.password === password)

    if (!user) {
      return res(
        ctx.status(401),
        ctx.json({
          error: 'Invalid credentials',
          code: 'INVALID_CREDENTIALS'
        })
      )
    }

    // Generate JWT-like token
    const token = generateMockToken(user)
    const refreshToken = generateMockToken(user, 'refresh')

    return res(
      ctx.json({
        user: {
          id: user.email === '[email protected]' ? 1 :
              user.email === '[email protected]' ? 2 : 3,
          email: user.email,
          role: user.role,
          permissions: getPermissionsForRole(user.role)
        },
        tokens: {
          accessToken: token,
          refreshToken: refreshToken,
          expiresIn: 3600
        }
      })
    )
  }),

  // Token refresh
  rest.post('/api/auth/refresh', async (req, res, ctx) => {
    const { refreshToken } = await req.json()

    if (!refreshToken) {
      return res(
        ctx.status(401),
        ctx.json({ error: 'Refresh token required' })
      )
    }

    // Validate refresh token (simplified)
    try {
      const payload = parseMockToken(refreshToken)

      if (payload.type !== 'refresh') {
        return res(
          ctx.status(401),
          ctx.json({ error: 'Invalid refresh token' })
        )
      }

      // Generate new access token
      const newAccessToken = generateMockToken(payload.user)

      return res(
        ctx.json({
          accessToken: newAccessToken,
          expiresIn: 3600
        })
      )
    } catch (error) {
      return res(
        ctx.status(401),
        ctx.json({ error: 'Invalid refresh token' })
      )
    }
  }),

  // Protected route middleware
  rest.get('/api/protected/profile', (req, res, ctx) => {
    const authHeader = req.headers.get('authorization')

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res(
        ctx.status(401),
        ctx.json({ error: 'Authorization required' })
      )
    }

    const token = authHeader.slice(7)

    try {
      const payload = parseMockToken(token)

      // Check token expiration
      if (payload.exp < Date.now() / 1000) {
        return res(
          ctx.status(401),
          ctx.json({ error: 'Token expired' })
        )
      }

      // Return user profile
      return res(
        ctx.json({
          user: payload.user,
          permissions: getPermissionsForRole(payload.user.role)
        })
      )
    } catch (error) {
      return res(
        ctx.status(401),
        ctx.json({ error: 'Invalid token' })
      )
    }
  })
]

// 3. WebSocket Mocking
export const wsHandlers = [
  // WebSocket connection for real-time chat
  rest.post('/api/chat/connect', (req, res, ctx) => {
    return res(
      ctx.json({
        wsUrl: 'wss://mock-chat.example.com/ws',
        userId: Math.random().toString(36).substr(2, 9),
        sessionId: generateMockSessionId()
      })
    )
  })
]

// WebSocket server simulation
export const mockWebSocketServer = {
  connections: new Map(),

  simulateConnection(userId, sessionId) {
    return {
      userId,
      sessionId,
      connected: true,
      lastActivity: Date.now(),
      messageCount: 0
    }
  },

  simulateMessage(fromUserId, toUserId, content) {
    return {
      id: Math.random().toString(36).substr(2, 9),
      from: fromUserId,
      to: toUserId,
      content,
      timestamp: Date.now(),
      status: 'delivered'
    }
  }
}

// 4. Performance Testing Handlers
export const performanceHandlers = [
  // Simulate slow API responses
  rest.get('/api/slow-endpoint', (req, res, ctx) => {
    const delay = parseInt(req.url.searchParams.get('delay')) || 2000

    return res(
      ctx.delay(delay),
      ctx.json({
        message: `Response delayed by ${delay}ms`,
        timestamp: Date.now()
      })
    )
  }),

  // Simulate large data responses
  rest.get('/api/large-dataset', (req, res, ctx) => {
    const size = parseInt(req.url.searchParams.get('size')) || 1000

    const largeDataset = Array.from({ length: size }, (_, index) => ({
      id: index + 1,
      name: `Item ${index + 1}`,
      description: `This is a detailed description for item ${index + 1}`,
      metadata: {
        category: `Category ${(index % 10) + 1}`,
        tags: [`tag${index % 5}`, `tag${(index + 1) % 5}`],
        rating: Math.random() * 5,
        price: parseFloat((Math.random() * 1000).toFixed(2)),
        inStock: Math.random() > 0.2,
        createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString()
      }
    }))

    return res(
      ctx.json({
        data: largeDataset,
        total: largeDataset.length,
        generatedAt: Date.now(),
        size: JSON.stringify(largeDataset).length
      })
    )
  }),

  // Simulate concurrent requests stress test
  rest.get('/api/stress-test', (req, res, ctx) => {
    const iterations = parseInt(req.url.searchParams.get('iterations')) || 100

    // Simulate CPU-intensive work
    const result = []
    for (let i = 0; i < iterations; i++) {
      result.push({
        iteration: i,
        fibonacci: fibonacci(Math.min(i, 30)),
        timestamp: Date.now()
      })
    }

    return res(
      ctx.delay(100),
      ctx.json({
        iterations,
        results: result,
        completedAt: Date.now()
      })
    )
  })
]

// 5. Error Simulation Handlers
export const errorHandlers = [
  // Configurable error scenarios
  rest.get('/api/error-scenario/:type', (req, res, ctx) => {
    const { type } = req.params

    switch (type) {
      case 'timeout':
        return res(
          ctx.delay(30000), // 30 second timeout
          ctx.status(200),
          ctx.json({ message: 'This will timeout' })
        )

      case 'network-error':
        return res.networkError('Network connection failed')

      case 'server-error':
        return res(
          ctx.status(500),
          ctx.json({
            error: 'Internal server error',
            details: 'Something went terribly wrong',
            timestamp: Date.now(),
            requestId: generateRequestId()
          })
        )

      case 'rate-limit':
        return res(
          ctx.status(429),
          ctx.set('Retry-After', '60'),
          ctx.json({
            error: 'Rate limit exceeded',
            message: 'Too many requests, please try again later',
            retryAfter: 60
          })
        )

      case 'maintenance':
        return res(
          ctx.status(503),
          ctx.set('Retry-After', '3600'),
          ctx.json({
            error: 'Service unavailable',
            message: 'We are currently under maintenance',
            estimatedDowntime: '1 hour'
          })
        )

      default:
        return res(
          ctx.status(400),
          ctx.json({
            error: 'Invalid error scenario',
            validScenarios: ['timeout', 'network-error', 'server-error', 'rate-limit', 'maintenance']
          })
        )
    }
  }),

  // Flaky endpoint that fails intermittently
  rest.get('/api/flaky-endpoint', (req, res, ctx) => {
    const failureRate = parseFloat(req.url.searchParams.get('failureRate')) || 0.3

    if (Math.random() < failureRate) {
      return res(
        ctx.status(500),
        ctx.json({
          error: 'Random failure occurred',
          failureRate,
          timestamp: Date.now()
        })
      )
    }

    return res(
      ctx.json({
        message: 'Success!',
        failureRate,
        timestamp: Date.now()
      })
    )
  })
]

// 6. Request/Response Interception and Modification
export const interceptionHandlers = [
  // Modify request data
  rest.post('/api/upload', async (req, res, ctx) => {
    const originalBody = await req.json()

    // Log and modify the request
    console.log('Original request:', originalBody)

    const modifiedBody = {
      ...originalBody,
      processedAt: Date.now(),
      enhanced: true
    }

    return res(
      ctx.status(200),
      ctx.json({
        message: 'File uploaded successfully',
        originalData: originalBody,
        processedData: modifiedBody,
        serverSide: true
      })
    )
  }),

  // Response transformation
  rest.get('/api/data/*', (req, res, ctx) => {
    // Let the original request go through
    return fetch(req).then(response => {
      if (!response.ok) {
        return response
      }

      return response.json().then(data => {
        // Transform the response
        const transformedData = {
          ...data,
          enhanced: true,
          processedAt: Date.now(),
          metadata: {
            requestUrl: req.url,
            method: req.method,
            userAgent: req.headers.get('user-agent')
          }
        }

        return res(
          ctx.json(transformedData)
        )
      })
    }).catch(() => {
      return res(
        ctx.status(500),
        ctx.json({ error: 'Failed to fetch original data' })
      )
    })
  })
]

// Utility Functions
function createSeededRandom(seed) {
  let m = 0x80000000
  let a = 1103515245
  let c = 12345
  let state = Math.abs(seed.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) || 1

  return function() {
    state = (a * state + c) % m
    return state / m
  }
}

function generateRandomName(random) {
  const firstNames = ['John', 'Jane', 'Bob', 'Alice', 'Charlie', 'Diana', 'Edward', 'Fiona']
  const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis']

  return firstNames[Math.floor(random() * firstNames.length)] + ' ' +
         lastNames[Math.floor(random() * lastNames.length)]
}

function generateRandomDomain(random) {
  const domains = ['example.com', 'test.org', 'demo.net', 'sample.io', 'mock.dev']
  return domains[Math.floor(random() * domains.length)]
}

function generateRandomBio(random) {
  const bios = [
    'Software developer passionate about clean code',
    'Tech enthusiast and lifelong learner',
    'Building amazing web applications',
    'Full-stack developer with creative flair',
    'Open source contributor and mentor'
  ]

  return bios[Math.floor(random() * bios.length)]
}

function generateRandomSkills(random) {
  const allSkills = ['JavaScript', 'React', 'Node.js', 'Python', 'TypeScript', 'GraphQL', 'Docker', 'AWS']
  const skillCount = Math.floor(random() * 4) + 2
  const selectedSkills = []

  for (let i = 0; i < skillCount; i++) {
    const skill = allSkills[Math.floor(random() * allSkills.length)]
    if (!selectedSkills.includes(skill)) {
      selectedSkills.push(skill)
    }
  }

  return selectedSkills
}

function generateMockToken(user, type = 'access') {
  const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
  const payload = btoa(JSON.stringify({
    sub: user.email,
    role: user.role,
    type,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + (type === 'refresh' ? 86400 : 3600),
    user: { email: user.email, role: user.role }
  }))
  const signature = btoa('mock-signature')

  return `${header}.${payload}.${signature}`
}

function parseMockToken(token) {
  const [, payload] = token.split('.')
  return JSON.parse(atob(payload))
}

function getPermissionsForRole(role) {
  const permissions = {
    admin: ['read', 'write', 'delete', 'manage_users'],
    moderator: ['read', 'write', 'moderate'],
    user: ['read', 'write_own']
  }

  return permissions[role] || ['read']
}

function generateMockSessionId() {
  return 'session_' + Math.random().toString(36).substr(2, 9)
}

function generateRequestId() {
  return 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
}

function fibonacci(n) {
  if (n <= 1) return n
  let a = 0, b = 1
  for (let i = 2; i <= n; i++) {
    [a, b] = [b, a + b]
  }
  return b
}

// Export all handlers
export const allHandlers = [
  ...dynamicHandlers,
  ...authHandlers,
  ...wsHandlers,
  ...performanceHandlers,
  ...errorHandlers,
  ...interceptionHandlers
]

// Setup comprehensive server
export const advancedServer = setupServer(...allHandlers)

// File: src/mocks/advancedBrowser.js
import { setupWorker } from 'msw/browser'
import { allHandlers } from './advancedHandlers'

export const worker = setupWorker(...allHandlers)

// Usage Example - Advanced React Component
// File: src/components/AdvancedApiDemo.js
import React, { useState, useEffect, useCallback } from 'react'

function AdvancedApiDemo() {
  const [dynamicUsers, setDynamicUsers] = useState([])
  const [stockData, setStockData] = useState(null)
  const [authUser, setAuthUser] = useState(null)
  const [performanceData, setPerformanceData] = useState(null)
  const [errorScenario, setErrorScenario] = useState('')
  const [loading, setLoading] = useState(false)

  // Generate dynamic users
  const generateUsers = useCallback(async (count = 10, seed = 'default') => {
    setLoading(true)
    try {
      const response = await fetch(`/api/dynamic-users/${count}?seed=${seed}`)
      const data = await response.json()
      setDynamicUsers(data.users)
    } catch (error) {
      console.error('Error generating users:', error)
    } finally {
      setLoading(false)
    }
  }, [])

  // Fetch stock data
  const fetchStockData = useCallback(async (symbol) => {
    try {
      const response = await fetch(`/api/stocks/${symbol}`)
      const data = await response.json()
      setStockData(data)
    } catch (error) {
      console.error('Error fetching stock data:', error)
    }
  }, [])

  // Login simulation
  const handleLogin = useCallback(async (email, password) => {
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      })

      const data = await response.json()

      if (response.ok) {
        setAuthUser(data.user)
        localStorage.setItem('accessToken', data.tokens.accessToken)
        return data
      } else {
        throw new Error(data.error)
      }
    } catch (error) {
      console.error('Login error:', error)
      throw error
    }
  }, [])

  // Fetch protected data
  const fetchProtectedData = useCallback(async () => {
    try {
      const token = localStorage.getItem('accessToken')
      const response = await fetch('/api/protected/profile', {
        headers: { 'Authorization': `Bearer ${token}` }
      })

      const data = await response.json()
      return data
    } catch (error) {
      console.error('Error fetching protected data:', error)
      throw error
    }
  }, [])

  // Performance test
  const runPerformanceTest = useCallback(async (iterations = 100) => {
    setLoading(true)
    try {
      const response = await fetch(`/api/stress-test?iterations=${iterations}`)
      const data = await response.json()
      setPerformanceData(data)
    } catch (error) {
      console.error('Performance test error:', error)
    } finally {
      setLoading(false)
    }
  }, [])

  // Test error scenarios
  const testErrorScenario = useCallback(async (type) => {
    setLoading(true)
    setErrorScenario(type)
    try {
      const response = await fetch(`/api/error-scenario/${type}`)
      const data = await response.json()
      console.log('Error scenario data:', data)
    } catch (error) {
      console.error(`Error scenario ${type}:`, error)
    } finally {
      setLoading(false)
      setErrorScenario('')
    }
  }, [])

  useEffect(() => {
    // Initialize with some data
    generateUsers(10, 'demo')
    fetchStockData('AAPL')
  }, [generateUsers, fetchStockData])

  return (
    <div style={{ padding: '2rem', fontFamily: 'Arial, sans-serif' }}>
      <h1>Advanced API Demo</h1>

      {/* Dynamic Users */}
      <section style={{ marginBottom: '2rem' }}>
        <h2>Dynamic User Generation</h2>
        <div style={{ marginBottom: '1rem' }}>
          <input
            type="number"
            placeholder="Count"
            defaultValue="10"
            style={{ marginRight: '0.5rem', padding: '0.5rem' }}
            onChange={(e) => {
              const count = parseInt(e.target.value)
              const seed = 'dynamic-' + Date.now()
              generateUsers(count, seed)
            }}
          />
          <button onClick={() => generateUsers(10, 'random-' + Date.now())}>
            Generate New Users
          </button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '1rem' }}>
          {dynamicUsers.map(user => (
            <div key={user.id} style={{ padding: '1rem', border: '1px solid #ddd', borderRadius: '8px' }}>
              <img src={user.avatar} alt={user.name} style={{ width: '50px', height: '50px', borderRadius: '50%' }} />
              <h4>{user.name}</h4>
              <p>{user.email}</p>
              <p>Age: {user.age}</p>
              <p>{user.bio}</p>
              <div>
                {user.skills.map(skill => (
                  <span key={skill} style={{
                    display: 'inline-block',
                    padding: '0.25rem 0.5rem',
                    margin: '0.125rem',
                    backgroundColor: '#e0e0e0',
                    borderRadius: '12px',
                    fontSize: '0.75rem'
                  }}>
                    {skill}
                  </span>
                ))}
              </div>
            </div>
          ))}
        </div>
      </section>

      {/* Stock Data */}
      <section style={{ marginBottom: '2rem' }}>
        <h2>Real-time Stock Simulation</h2>
        <div style={{ marginBottom: '1rem' }}>
          <input
            type="text"
            placeholder="Stock symbol"
            defaultValue="AAPL"
            onChange={(e) => fetchStockData(e.target.value)}
            style={{ padding: '0.5rem', marginRight: '0.5rem' }}
          />
        </div>
        {stockData && (
          <div style={{ padding: '1rem', backgroundColor: '#f5f5f5', borderRadius: '8px' }}>
            <h3>{stockData.symbol}</h3>
            <p style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>
              ${stockData.currentPrice}
            </p>
            <p style={{ color: stockData.change >= 0 ? 'green' : 'red' }}>
              {stockData.change >= 0 ? '+' : ''}{stockData.change} ({stockData.changePercent}%)
            </p>
            <p>Volume: {stockData.volume.toLocaleString()}</p>
          </div>
        )}
      </section>

      {/* Authentication Demo */}
      <section style={{ marginBottom: '2rem' }}>
        <h2>Authentication Flow</h2>
        {!authUser ? (
          <div>
            <button onClick={() => handleLogin('[email protected]', 'admin123')}>
              Login as Admin
            </button>
            <button onClick={() => handleLogin('[email protected]', 'user123')} style={{ marginLeft: '0.5rem' }}>
              Login as User
            </button>
          </div>
        ) : (
          <div>
            <p>Logged in as: {authUser.email}</p>
            <p>Role: {authUser.role}</p>
            <p>Permissions: {authUser.permissions.join(', ')}</p>
            <button onClick={async () => {
              try {
                await fetchProtectedData()
                alert('Protected data accessed successfully!')
              } catch (error) {
                alert('Error accessing protected data: ' + error.message)
              }
            }}>
              Test Protected Route
            </button>
            <button onClick={() => {
              setAuthUser(null)
              localStorage.removeItem('accessToken')
            }} style={{ marginLeft: '0.5rem' }}>
              Logout
            </button>
          </div>
        )}
      </section>

      {/* Performance Testing */}
      <section style={{ marginBottom: '2rem' }}>
        <h2>Performance Testing</h2>
        <div style={{ marginBottom: '1rem' }}>
          <input
            type="number"
            placeholder="Iterations"
            defaultValue="100"
            style={{ marginRight: '0.5rem', padding: '0.5rem' }}
          />
          <button onClick={() => {
            const input = document.querySelector('input[placeholder="Iterations"]')
            runPerformanceTest(parseInt(input.value))
          }}>
            Run Stress Test
          </button>
        </div>
        {performanceData && (
          <div style={{ padding: '1rem', backgroundColor: '#f0f0f0', borderRadius: '8px' }}>
            <p>Completed {performanceData.iterations} iterations</p>
            <p>Results processed in {performanceData.completedAt - performanceData.results[0].timestamp}ms</p>
            <p>Final Fibonacci value: {performanceData.results[performanceData.results.length - 1]?.fibonacci}</p>
          </div>
        )}
      </section>

      {/* Error Scenarios */}
      <section style={{ marginBottom: '2rem' }}>
        <h2>Error Scenario Testing</h2>
        <div style={{ marginBottom: '1rem' }}>
          {['timeout', 'network-error', 'server-error', 'rate-limit', 'maintenance'].map(type => (
            <button
              key={type}
              onClick={() => testErrorScenario(type)}
              disabled={loading && errorScenario === type}
              style={{ marginRight: '0.5rem', marginBottom: '0.5rem', padding: '0.5rem' }}
            >
              Test {type.replace('-', ' ')}
            </button>
          ))}
        </div>
        {errorScenario && (
          <div style={{ padding: '1rem', backgroundColor: '#fff3cd', borderRadius: '8px' }}>
            <p>Testing error scenario: <strong>{errorScenario}</strong></p>
            {loading && <p>Loading... (this may take a while for timeout scenarios)</p>}
          </div>
        )}
      </section>

      {loading && (
        <div style={{
          position: 'fixed',
          top: 0,
          left: 0,
          right: 0,
          bottom: 0,
          backgroundColor: 'rgba(0, 0, 0, 0.5)',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center'
        }}>
          <div style={{ backgroundColor: 'white', padding: '2rem', borderRadius: '8px' }}>
            <p>Loading...</p>
          </div>
        </div>
      )}
    </div>
  )
}