Примеры Mock Service Worker

Комплексные примеры MSW (Mock Service Worker), включая мокирование API, GraphQL, WebSocket и продвинутые паттерны обработки запросов

💻 Основы мокирования REST API javascript

🟢 simple ⭐⭐

Фундаментальное мокирование REST API, охватывающее операции GET, POST, PUT, DELETE и обработку ответов

⏱️ 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'

export default 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>
  )
}

💻 Мокирование GraphQL API javascript

🟡 intermediate ⭐⭐⭐⭐

Полное мокирование GraphQL, включая запросы, мутации, подписки и сложные связи данных

⏱️ 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
    }
  }
`

export default 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>
  )
}

💻 Продвинутые паттерны MSW javascript

🔴 complex ⭐⭐⭐⭐⭐

Продвинутые техники MSW, включая динамические ответы, мокирование WebSocket, потоки аутентификации и тестирование производительности

⏱️ 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'

export default 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>
  )
}