API Testing Guide

Testing your StateSet API integration is crucial for ensuring reliability, catching bugs early, and maintaining confidence in your application. This guide covers comprehensive testing strategies from unit tests to end-to-end integration testing.

Unit Testing

Test individual API calls and error handling

Integration Testing

Test complete workflows and data flow

Mock Testing

Test without hitting live APIs

E2E Testing

Test real user scenarios end-to-end

Testing Strategy Overview

Test Pyramid for API Integration

    🔺 E2E Tests (Few)
       ↗ Complex user workflows
       ↗ Critical business paths
       ↗ Cross-system integration
   
  🔷 Integration Tests (Some)  
     ↗ API endpoint interactions
     ↗ Data transformation
     ↗ Error handling flows
     
🔲 Unit Tests (Many)
   ↗ Individual functions
   ↗ Input validation
   ↗ Business logic
   ↗ Error scenarios

Environment Setup

Test Environment Configuration

// test/setup.js
import { StateSetClient } from 'stateset-node';
import dotenv from 'dotenv';

// Load test environment variables
dotenv.config({ path: '.env.test' });

// Test configuration
export const testConfig = {
  apiKey: process.env.STATESET_TEST_API_KEY,
  baseUrl: process.env.STATESET_TEST_BASE_URL || 'https://api.sandbox.stateset.com/v1',
  timeout: 10000,
  retries: 3
};

// Create test client
export const testClient = new StateSetClient({
  apiKey: testConfig.apiKey,
  baseUrl: testConfig.baseUrl,
  timeout: testConfig.timeout
});

// Test data factory
export const createTestData = {
  customer: (overrides = {}) => ({
    email: `test+${Date.now()}@example.com`,
    first_name: 'Test',
    last_name: 'Customer',
    phone: '+1-555-123-4567',
    ...overrides
  }),
  
  order: (customerId, overrides = {}) => ({
    customer_id: customerId,
    items: [
      {
        sku: 'TEST-ITEM-001',
        quantity: 1,
        price: 29.99
      }
    ],
    currency: 'USD',
    ...overrides
  }),
  
  address: (overrides = {}) => ({
    street1: '123 Test Street',
    city: 'Test City',
    state: 'CA',
    postal_code: '12345',
    country: 'US',
    ...overrides
  })
};

// Test helpers
export const testHelpers = {
  generateUniqueEmail: () => `test+${Date.now()}+${Math.random().toString(36)}@example.com`,
  
  waitFor: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
  
  retry: async (fn, maxAttempts = 3, delay = 1000) => {
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await fn();
      } catch (error) {
        if (attempt === maxAttempts) throw error;
        await testHelpers.waitFor(delay * attempt);
      }
    }
  },
  
  cleanupResources: async (resources) => {
    for (const resource of resources) {
      try {
        await resource.cleanup();
      } catch (error) {
        console.warn('Cleanup failed:', error.message);
      }
    }
  }
};

Environment Variables

# .env.test
STATESET_TEST_API_KEY=sk_test_51HqJx2eZvKYlo2CXcQhJZPiQdoJO4v_FGtbA8QTy9E4tY
STATESET_TEST_BASE_URL=https://api.sandbox.stateset.com/v1
NODE_ENV=test
LOG_LEVEL=warn

# Test database (if applicable)
TEST_DATABASE_URL=postgresql://user:pass@localhost:5432/test_db

# External service mocks
MOCK_STRIPE_ENABLED=true
MOCK_EMAIL_ENABLED=true

Unit Testing

Testing API Client Methods

// test/unit/statesetClient.test.js
import { jest } from '@jest/globals';
import { StateSetClient } from 'stateset-node';
import { StateSetAPIError } from 'stateset-node/errors';

describe('StateSetClient', () => {
  let client;
  let mockFetch;

  beforeEach(() => {
    mockFetch = jest.fn();
    global.fetch = mockFetch;
    
    client = new StateSetClient({
      apiKey: 'sk_test_123',
      baseUrl: 'https://api.test.stateset.com/v1'
    });
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  describe('customers.create', () => {
    it('should create customer successfully', async () => {
      const customerData = {
        email: 'test@example.com',
        first_name: 'John',
        last_name: 'Doe'
      };

      const expectedResponse = {
        id: 'cust_123',
        ...customerData,
        created_at: '2024-01-15T10:30:00Z'
      };

      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 201,
        json: async () => expectedResponse
      });

      const result = await client.customers.create(customerData);

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.test.stateset.com/v1/customers',
        {
          method: 'POST',
          headers: {
            'Authorization': 'Bearer sk_test_123',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(customerData)
        }
      );

      expect(result).toEqual(expectedResponse);
    });

    it('should handle validation errors', async () => {
      const invalidCustomerData = {
        email: 'invalid-email',
        first_name: '',
        last_name: 'Doe'
      };

      const errorResponse = {
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Request validation failed',
          errors: [
            { field: 'email', message: 'Invalid email format' },
            { field: 'first_name', message: 'First name is required' }
          ]
        }
      };

      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: async () => errorResponse
      });

      await expect(client.customers.create(invalidCustomerData))
        .rejects.toThrow(StateSetAPIError);

      try {
        await client.customers.create(invalidCustomerData);
      } catch (error) {
        expect(error.code).toBe('VALIDATION_ERROR');
        expect(error.details.errors).toHaveLength(2);
        expect(error.details.errors[0].field).toBe('email');
      }
    });

    it('should handle network errors', async () => {
      mockFetch.mockRejectedValueOnce(new Error('Network error'));

      await expect(client.customers.create({}))
        .rejects.toThrow('Network error');
    });

    it('should handle rate limiting with retry', async () => {
      const customerData = { email: 'test@example.com' };
      const successResponse = { id: 'cust_123', ...customerData };

      // First call returns rate limit error
      mockFetch
        .mockResolvedValueOnce({
          ok: false,
          status: 429,
          headers: new Map([['Retry-After', '1']]),
          json: async () => ({
            error: {
              code: 'RATE_LIMITED',
              message: 'Rate limit exceeded'
            }
          })
        })
        // Second call succeeds
        .mockResolvedValueOnce({
          ok: true,
          status: 201,
          json: async () => successResponse
        });

      const result = await client.customers.create(customerData);

      expect(mockFetch).toHaveBeenCalledTimes(2);
      expect(result).toEqual(successResponse);
    });
  });

  describe('orders.list', () => {
    it('should list orders with filters', async () => {
      const filters = {
        status: 'shipped',
        created_after: '2024-01-01',
        limit: 10
      };

      const expectedResponse = {
        orders: [
          { id: 'ord_1', status: 'shipped' },
          { id: 'ord_2', status: 'shipped' }
        ],
        pagination: {
          has_more: false,
          total_count: 2
        }
      };

      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => expectedResponse
      });

      const result = await client.orders.list(filters);

      // Verify URL construction with query parameters
      const expectedUrl = 'https://api.test.stateset.com/v1/orders?' +
        'status=shipped&created_after=2024-01-01&limit=10';
      
      expect(mockFetch).toHaveBeenCalledWith(
        expectedUrl,
        expect.objectContaining({
          method: 'GET',
          headers: expect.objectContaining({
            'Authorization': 'Bearer sk_test_123'
          })
        })
      );

      expect(result).toEqual(expectedResponse);
    });
  });
});

Testing Error Handling

// test/unit/errorHandling.test.js
import { StateSetErrorHandler } from '../../src/utils/errorHandler';

describe('StateSetErrorHandler', () => {
  describe('validation errors', () => {
    it('should extract field errors correctly', () => {
      const error = {
        code: 'VALIDATION_ERROR',
        message: 'Request validation failed',
        details: {
          errors: [
            { field: 'email', message: 'Invalid email format' },
            { field: 'phone', message: 'Phone number required' }
          ]
        }
      };

      const result = StateSetErrorHandler.handle(error);

      expect(result.type).toBe('validation');
      expect(result.fieldErrors).toEqual({
        email: 'Invalid email format',
        phone: 'Phone number required'
      });
    });
  });

  describe('rate limiting', () => {
    it('should handle rate limit with retry delay', () => {
      const error = {
        code: 'RATE_LIMITED',
        details: { retry_after: 30 }
      };

      const result = StateSetErrorHandler.handle(error);

      expect(result.type).toBe('rate_limit');
      expect(result.retryAfter).toBe(30);
      expect(result.message).toContain('30 seconds');
    });
  });

  describe('duplicate resources', () => {
    it('should suggest appropriate action for duplicate email', () => {
      const error = {
        code: 'DUPLICATE_EMAIL',
        details: {
          email: 'test@example.com',
          existing_customer_id: 'cust_123'
        }
      };

      const result = StateSetErrorHandler.handle(error);

      expect(result.type).toBe('duplicate');
      expect(result.suggestion).toBe('Try logging in instead');
    });
  });
});

Testing Business Logic

// test/unit/orderProcessor.test.js
import { OrderProcessor } from '../../src/services/orderProcessor';
import { StateSetClient } from 'stateset-node';

jest.mock('stateset-node');

describe('OrderProcessor', () => {
  let orderProcessor;
  let mockClient;

  beforeEach(() => {
    mockClient = {
      customers: {
        create: jest.fn(),
        get: jest.fn()
      },
      orders: {
        create: jest.fn()
      },
      inventory: {
        check: jest.fn(),
        reserve: jest.fn()
      }
    };

    StateSetClient.mockImplementation(() => mockClient);
    orderProcessor = new OrderProcessor();
  });

  describe('processOrder', () => {
    it('should create customer and order successfully', async () => {
      const orderData = {
        customer: {
          email: 'test@example.com',
          first_name: 'John',
          last_name: 'Doe'
        },
        items: [
          { sku: 'ITEM-001', quantity: 1, price: 29.99 }
        ]
      };

      // Mock customer creation
      mockClient.customers.create.mockResolvedValue({
        id: 'cust_123',
        email: 'test@example.com'
      });

      // Mock inventory check
      mockClient.inventory.check.mockResolvedValue({
        available: true,
        items: [{ sku: 'ITEM-001', available: true, quantity: 10 }]
      });

      // Mock order creation
      mockClient.orders.create.mockResolvedValue({
        id: 'ord_123',
        status: 'pending',
        customer_id: 'cust_123'
      });

      const result = await orderProcessor.processOrder(orderData);

      expect(mockClient.customers.create).toHaveBeenCalledWith(orderData.customer);
      expect(mockClient.inventory.check).toHaveBeenCalledWith({
        items: orderData.items
      });
      expect(mockClient.orders.create).toHaveBeenCalledWith({
        customer_id: 'cust_123',
        items: orderData.items
      });

      expect(result.orderId).toBe('ord_123');
      expect(result.customerId).toBe('cust_123');
    });

    it('should handle insufficient inventory', async () => {
      const orderData = {
        customer: { email: 'test@example.com' },
        items: [{ sku: 'ITEM-001', quantity: 5 }]
      };

      mockClient.inventory.check.mockResolvedValue({
        available: false,
        items: [{ sku: 'ITEM-001', available: false, quantity: 2 }]
      });

      await expect(orderProcessor.processOrder(orderData))
        .rejects.toThrow('Insufficient inventory');

      expect(mockClient.orders.create).not.toHaveBeenCalled();
    });

    it('should handle existing customer', async () => {
      const orderData = {
        customer: { email: 'existing@example.com' },
        items: [{ sku: 'ITEM-001', quantity: 1 }]
      };

      // Customer creation fails with duplicate email
      mockClient.customers.create.mockRejectedValue({
        code: 'DUPLICATE_EMAIL',
        details: { existing_customer_id: 'cust_existing' }
      });

      // Get existing customer
      mockClient.customers.get.mockResolvedValue({
        id: 'cust_existing',
        email: 'existing@example.com'
      });

      mockClient.inventory.check.mockResolvedValue({ available: true });
      mockClient.orders.create.mockResolvedValue({
        id: 'ord_456',
        customer_id: 'cust_existing'
      });

      const result = await orderProcessor.processOrder(orderData);

      expect(mockClient.customers.get).toHaveBeenCalledWith('cust_existing');
      expect(result.customerId).toBe('cust_existing');
    });
  });
});

Integration Testing

Testing API Endpoints

// test/integration/customers.test.js
import { testClient, createTestData, testHelpers } from '../setup.js';

describe('Customer API Integration', () => {
  const createdResources = [];

  afterEach(async () => {
    await testHelpers.cleanupResources(createdResources);
    createdResources.length = 0;
  });

  describe('Customer lifecycle', () => {
    it('should create, update, and delete customer', async () => {
      // Create customer
      const customerData = createTestData.customer();
      const customer = await testClient.customers.create(customerData);

      createdResources.push({
        cleanup: () => testClient.customers.delete(customer.id)
      });

      expect(customer.id).toBeDefined();
      expect(customer.email).toBe(customerData.email);
      expect(customer.status).toBe('active');

      // Update customer
      const updateData = {
        first_name: 'Updated',
        customer_tier: 'gold'
      };

      const updatedCustomer = await testClient.customers.update(
        customer.id,
        updateData
      );

      expect(updatedCustomer.first_name).toBe('Updated');
      expect(updatedCustomer.customer_tier).toBe('gold');
      expect(updatedCustomer.email).toBe(customerData.email); // Should remain unchanged

      // Get customer
      const retrievedCustomer = await testClient.customers.get(customer.id);
      expect(retrievedCustomer).toEqual(updatedCustomer);

      // List customers (should include our customer)
      const customers = await testClient.customers.list({
        email: customerData.email
      });

      expect(customers.customers).toHaveLength(1);
      expect(customers.customers[0].id).toBe(customer.id);
    });

    it('should prevent duplicate email addresses', async () => {
      const customerData = createTestData.customer();
      
      // Create first customer
      const customer1 = await testClient.customers.create(customerData);
      createdResources.push({
        cleanup: () => testClient.customers.delete(customer1.id)
      });

      // Try to create second customer with same email
      await expect(testClient.customers.create(customerData))
        .rejects.toMatchObject({
          code: 'DUPLICATE_EMAIL',
          details: expect.objectContaining({
            email: customerData.email,
            existing_customer_id: customer1.id
          })
        });
    });

    it('should validate required fields', async () => {
      const invalidData = {
        email: 'invalid-email',
        first_name: '', // Required but empty
        last_name: 'Test'
      };

      await expect(testClient.customers.create(invalidData))
        .rejects.toMatchObject({
          code: 'VALIDATION_ERROR',
          details: expect.objectContaining({
            errors: expect.arrayContaining([
              expect.objectContaining({
                field: 'email',
                message: expect.stringContaining('Invalid email format')
              }),
              expect.objectContaining({
                field: 'first_name',
                message: expect.stringContaining('required')
              })
            ])
          })
        });
    });
  });

  describe('Customer search and filtering', () => {
    beforeEach(async () => {
      // Create test customers with different tiers and statuses
      const customers = [
        { ...createTestData.customer(), customer_tier: 'bronze', first_name: 'Alice' },
        { ...createTestData.customer(), customer_tier: 'gold', first_name: 'Bob' },
        { ...createTestData.customer(), customer_tier: 'platinum', first_name: 'Charlie' }
      ];

      for (const customerData of customers) {
        const customer = await testClient.customers.create(customerData);
        createdResources.push({
          cleanup: () => testClient.customers.delete(customer.id)
        });
      }

      // Wait for eventual consistency
      await testHelpers.waitFor(1000);
    });

    it('should filter customers by tier', async () => {
      const goldCustomers = await testClient.customers.list({
        customer_tier: 'gold'
      });

      expect(goldCustomers.customers).toHaveLength(1);
      expect(goldCustomers.customers[0].first_name).toBe('Bob');
    });

    it('should search customers by name', async () => {
      const searchResults = await testClient.customers.list({
        search: 'Alice'
      });

      expect(searchResults.customers).toHaveLength(1);
      expect(searchResults.customers[0].first_name).toBe('Alice');
    });

    it('should paginate results correctly', async () => {
      const page1 = await testClient.customers.list({
        limit: 2,
        offset: 0
      });

      expect(page1.customers).toHaveLength(2);
      expect(page1.pagination.has_more).toBe(true);

      const page2 = await testClient.customers.list({
        limit: 2,
        offset: 2
      });

      expect(page2.customers).toHaveLength(1);
      expect(page2.pagination.has_more).toBe(false);

      // Ensure no overlap between pages
      const page1Ids = page1.customers.map(c => c.id);
      const page2Ids = page2.customers.map(c => c.id);
      expect(page1Ids).not.toEqual(expect.arrayContaining(page2Ids));
    });
  });
});

Testing Complex Workflows

// test/integration/orderWorkflow.test.js
import { testClient, createTestData, testHelpers } from '../setup.js';

describe('Order Workflow Integration', () => {
  const createdResources = [];

  afterEach(async () => {
    await testHelpers.cleanupResources(createdResources);
    createdResources.length = 0;
  });

  describe('Complete order lifecycle', () => {
    it('should process order from creation to fulfillment', async () => {
      // 1. Create customer
      const customerData = createTestData.customer();
      const customer = await testClient.customers.create(customerData);
      
      createdResources.push({
        cleanup: () => testClient.customers.delete(customer.id)
      });

      // 2. Create order
      const orderData = createTestData.order(customer.id);
      const order = await testClient.orders.create(orderData);
      
      createdResources.push({
        cleanup: () => testClient.orders.delete(order.id)
      });

      expect(order.status).toBe('pending');
      expect(order.customer_id).toBe(customer.id);

      // 3. Process payment (mock payment in sandbox)
      const payment = await testClient.orders.pay(order.id, {
        payment_method: {
          type: 'card',
          card: {
            number: '4242424242424242', // Test card
            exp_month: 12,
            exp_year: 2025,
            cvc: '123'
          }
        }
      });

      expect(payment.status).toBe('succeeded');

      // 4. Check order status updated
      const paidOrder = await testClient.orders.get(order.id);
      expect(paidOrder.status).toBe('paid');
      expect(paidOrder.payment_status).toBe('paid');

      // 5. Ship order
      const shipment = await testClient.orders.ship(order.id, {
        carrier: 'fedex',
        tracking_number: 'TEST123456789',
        items: orderData.items
      });

      expect(shipment.tracking_number).toBe('TEST123456789');

      // 6. Verify final status
      const shippedOrder = await testClient.orders.get(order.id);
      expect(shippedOrder.status).toBe('shipped');
      expect(shippedOrder.tracking_number).toBe('TEST123456789');

      // 7. Create return
      const returnRequest = await testClient.returns.create({
        order_id: order.id,
        items: [
          {
            sku: orderData.items[0].sku,
            quantity: 1,
            reason: 'not_as_described'
          }
        ],
        customer_email: customer.email,
        reason_code: 'not_satisfied'
      });

      expect(returnRequest.status).toBe('requested');
      expect(returnRequest.order_id).toBe(order.id);

      createdResources.push({
        cleanup: () => testClient.returns.delete(returnRequest.id)
      });
    });

    it('should handle inventory constraints', async () => {
      const customer = await testClient.customers.create(createTestData.customer());
      createdResources.push({
        cleanup: () => testClient.customers.delete(customer.id)
      });

      // Try to order more items than available
      const orderData = {
        customer_id: customer.id,
        items: [
          {
            sku: 'LIMITED-STOCK-ITEM',
            quantity: 999999, // Exceeds available inventory
            price: 29.99
          }
        ]
      };

      await expect(testClient.orders.create(orderData))
        .rejects.toMatchObject({
          code: 'INSUFFICIENT_INVENTORY',
          details: expect.objectContaining({
            unavailable_items: expect.arrayContaining(['LIMITED-STOCK-ITEM'])
          })
        });
    });
  });

  describe('Bulk operations', () => {
    it('should handle bulk customer creation', async () => {
      const customerBatch = Array.from({ length: 5 }, () => createTestData.customer());

      const results = await Promise.all(
        customerBatch.map(data => testClient.customers.create(data))
      );

      // Clean up created customers
      results.forEach(customer => {
        createdResources.push({
          cleanup: () => testClient.customers.delete(customer.id)
        });
      });

      expect(results).toHaveLength(5);
      results.forEach((customer, index) => {
        expect(customer.email).toBe(customerBatch[index].email);
        expect(customer.id).toBeDefined();
      });
    });

    it('should respect rate limits during bulk operations', async () => {
      const customerBatch = Array.from({ length: 20 }, () => createTestData.customer());

      // Create customers with proper rate limiting
      const results = [];
      for (const customerData of customerBatch) {
        try {
          const customer = await testClient.customers.create(customerData);
          results.push(customer);
          
          createdResources.push({
            cleanup: () => testClient.customers.delete(customer.id)
          });
          
          // Small delay to avoid hitting rate limits
          await testHelpers.waitFor(100);
        } catch (error) {
          if (error.code === 'RATE_LIMITED') {
            // Wait for rate limit reset and retry
            await testHelpers.waitFor(error.details.retry_after * 1000);
            const customer = await testClient.customers.create(customerData);
            results.push(customer);
          } else {
            throw error;
          }
        }
      }

      expect(results).toHaveLength(20);
    });
  });
});

Mock Testing

Setting Up API Mocks

// test/mocks/statesetMock.js
import { rest } from 'msw';
import { setupServer } from 'msw/node';

// Mock data store
const mockData = {
  customers: new Map(),
  orders: new Map(),
  returns: new Map()
};

// ID generators
const generateId = (prefix) => `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2)}`;

// Mock API handlers
const handlers = [
  // Customer endpoints
  rest.post('https://api.sandbox.stateset.com/v1/customers', (req, res, ctx) => {
    const customerData = req.body;
    
    // Validate required fields
    if (!customerData.email || !customerData.first_name || !customerData.last_name) {
      return res(
        ctx.status(400),
        ctx.json({
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Missing required fields',
            errors: [
              ...(customerData.email ? [] : [{ field: 'email', message: 'Email is required' }]),
              ...(customerData.first_name ? [] : [{ field: 'first_name', message: 'First name is required' }]),
              ...(customerData.last_name ? [] : [{ field: 'last_name', message: 'Last name is required' }])
            ]
          }
        })
      );
    }

    // Check for duplicate email
    const existingCustomer = Array.from(mockData.customers.values())
      .find(c => c.email === customerData.email);
    
    if (existingCustomer) {
      return res(
        ctx.status(409),
        ctx.json({
          error: {
            code: 'DUPLICATE_EMAIL',
            message: 'Customer with this email already exists',
            details: {
              email: customerData.email,
              existing_customer_id: existingCustomer.id
            }
          }
        })
      );
    }

    // Create customer
    const customer = {
      id: generateId('cust'),
      ...customerData,
      status: 'active',
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    };

    mockData.customers.set(customer.id, customer);

    return res(
      ctx.status(201),
      ctx.json(customer)
    );
  }),

  rest.get('https://api.sandbox.stateset.com/v1/customers/:id', (req, res, ctx) => {
    const { id } = req.params;
    const customer = mockData.customers.get(id);

    if (!customer) {
      return res(
        ctx.status(404),
        ctx.json({
          error: {
            code: 'RESOURCE_NOT_FOUND',
            message: 'Customer not found'
          }
        })
      );
    }

    return res(ctx.json(customer));
  }),

  rest.get('https://api.sandbox.stateset.com/v1/customers', (req, res, ctx) => {
    const searchParams = req.url.searchParams;
    const limit = parseInt(searchParams.get('limit')) || 20;
    const offset = parseInt(searchParams.get('offset')) || 0;
    const email = searchParams.get('email');
    const tier = searchParams.get('customer_tier');

    let customers = Array.from(mockData.customers.values());

    // Apply filters
    if (email) {
      customers = customers.filter(c => c.email === email);
    }
    if (tier) {
      customers = customers.filter(c => c.customer_tier === tier);
    }

    // Apply pagination
    const totalCount = customers.length;
    const paginatedCustomers = customers.slice(offset, offset + limit);

    return res(
      ctx.json({
        customers: paginatedCustomers,
        pagination: {
          has_more: offset + limit < totalCount,
          total_count: totalCount
        }
      })
    );
  }),

  // Order endpoints
  rest.post('https://api.sandbox.stateset.com/v1/orders', (req, res, ctx) => {
    const orderData = req.body;

    // Validate customer exists
    if (!mockData.customers.has(orderData.customer_id)) {
      return res(
        ctx.status(400),
        ctx.json({
          error: {
            code: 'INVALID_CUSTOMER',
            message: 'Customer not found'
          }
        })
      );
    }

    // Check inventory (simplified)
    const hasLimitedStock = orderData.items.some(item => 
      item.sku === 'LIMITED-STOCK-ITEM' && item.quantity > 10
    );

    if (hasLimitedStock) {
      return res(
        ctx.status(422),
        ctx.json({
          error: {
            code: 'INSUFFICIENT_INVENTORY',
            message: 'Insufficient inventory',
            details: {
              unavailable_items: ['LIMITED-STOCK-ITEM']
            }
          }
        })
      );
    }

    const order = {
      id: generateId('ord'),
      ...orderData,
      status: 'pending',
      payment_status: 'pending',
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    };

    mockData.orders.set(order.id, order);

    return res(
      ctx.status(201),
      ctx.json(order)
    );
  }),

  // Rate limiting simulation
  rest.use((req, res, ctx) => {
    // Simulate rate limiting for high-frequency requests
    const isRateLimited = Math.random() < 0.1; // 10% chance
    
    if (isRateLimited && req.method === 'POST') {
      return res(
        ctx.status(429),
        ctx.json({
          error: {
            code: 'RATE_LIMITED',
            message: 'Rate limit exceeded',
            details: {
              retry_after: 2
            }
          }
        })
      );
    }
  })
];

// Create mock server
export const mockServer = setupServer(...handlers);

// Helper functions
export const mockHelpers = {
  resetData: () => {
    mockData.customers.clear();
    mockData.orders.clear();
    mockData.returns.clear();
  },

  addCustomer: (customer) => {
    const id = customer.id || generateId('cust');
    const fullCustomer = {
      id,
      status: 'active',
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
      ...customer
    };
    mockData.customers.set(id, fullCustomer);
    return fullCustomer;
  },

  getCustomers: () => Array.from(mockData.customers.values()),
  getOrders: () => Array.from(mockData.orders.values())
};

Using Mocks in Tests

// test/unit/customerService.mock.test.js
import { mockServer, mockHelpers } from '../mocks/statesetMock.js';
import { CustomerService } from '../../src/services/customerService.js';

describe('CustomerService with mocks', () => {
  let customerService;

  beforeAll(() => {
    mockServer.listen();
  });

  beforeEach(() => {
    mockHelpers.resetData();
    customerService = new CustomerService({
      apiKey: 'sk_test_mock',
      baseUrl: 'https://api.sandbox.stateset.com/v1'
    });
  });

  afterEach(() => {
    mockServer.resetHandlers();
  });

  afterAll(() => {
    mockServer.close();
  });

  it('should create and retrieve customer', async () => {
    const customerData = {
      email: 'test@example.com',
      first_name: 'John',
      last_name: 'Doe'
    };

    // Create customer
    const customer = await customerService.createCustomer(customerData);
    expect(customer.id).toBeDefined();
    expect(customer.email).toBe(customerData.email);

    // Retrieve customer
    const retrievedCustomer = await customerService.getCustomer(customer.id);
    expect(retrievedCustomer).toEqual(customer);
  });

  it('should handle validation errors', async () => {
    const invalidData = {
      email: 'test@example.com',
      // Missing required fields
    };

    await expect(customerService.createCustomer(invalidData))
      .rejects.toMatchObject({
        code: 'VALIDATION_ERROR'
      });
  });

  it('should handle duplicate emails', async () => {
    const customerData = {
      email: 'duplicate@example.com',
      first_name: 'John',
      last_name: 'Doe'
    };

    // Create first customer
    await customerService.createCustomer(customerData);

    // Try to create duplicate
    await expect(customerService.createCustomer(customerData))
      .rejects.toMatchObject({
        code: 'DUPLICATE_EMAIL'
      });
  });

  it('should paginate customer list', async () => {
    // Create multiple customers
    const customers = [];
    for (let i = 0; i < 5; i++) {
      customers.push(mockHelpers.addCustomer({
        email: `test${i}@example.com`,
        first_name: `Test${i}`,
        last_name: 'User'
      }));
    }

    // Get first page
    const page1 = await customerService.listCustomers({ limit: 2, offset: 0 });
    expect(page1.customers).toHaveLength(2);
    expect(page1.pagination.has_more).toBe(true);

    // Get second page
    const page2 = await customerService.listCustomers({ limit: 2, offset: 2 });
    expect(page2.customers).toHaveLength(2);
    expect(page2.pagination.has_more).toBe(true);

    // Get third page
    const page3 = await customerService.listCustomers({ limit: 2, offset: 4 });
    expect(page3.customers).toHaveLength(1);
    expect(page3.pagination.has_more).toBe(false);
  });
});

End-to-End Testing

E2E Test Setup

// test/e2e/setup.js
import { Browser, chromium } from '@playwright/test';
import { testClient } from '../setup.js';

export class E2ETestEnvironment {
  constructor() {
    this.browser = null;
    this.context = null;
    this.page = null;
    this.baseUrl = process.env.E2E_BASE_URL || 'http://localhost:3000';
  }

  async setup() {
    this.browser = await chromium.launch({
      headless: process.env.CI === 'true'
    });
    
    this.context = await this.browser.newContext({
      // Record videos for failed tests
      recordVideo: {
        dir: 'test-results/videos/',
        size: { width: 1280, height: 720 }
      }
    });

    this.page = await this.context.newPage();

    // Setup API interception for debugging
    this.page.on('request', request => {
      if (request.url().includes('api.stateset.com')) {
        console.log('API Request:', request.method(), request.url());
      }
    });

    this.page.on('response', response => {
      if (response.url().includes('api.stateset.com')) {
        console.log('API Response:', response.status(), response.url());
      }
    });
  }

  async teardown() {
    await this.page?.close();
    await this.context?.close();
    await this.browser?.close();
  }

  async createTestCustomer() {
    const customerData = {
      email: `e2e-test-${Date.now()}@example.com`,
      first_name: 'E2E',
      last_name: 'Test',
      phone: '+1-555-123-4567'
    };

    return await testClient.customers.create(customerData);
  }

  async navigateTo(path) {
    await this.page.goto(`${this.baseUrl}${path}`);
  }

  async waitForApiCall(urlPattern) {
    return await this.page.waitForResponse(
      response => response.url().includes(urlPattern) && response.status() === 200
    );
  }
}

E2E User Journey Tests

// test/e2e/customerJourney.test.js
import { test, expect } from '@playwright/test';
import { E2ETestEnvironment } from './setup.js';

test.describe('Customer Journey E2E', () => {
  let testEnv;

  test.beforeEach(async () => {
    testEnv = new E2ETestEnvironment();
    await testEnv.setup();
  });

  test.afterEach(async () => {
    await testEnv.teardown();
  });

  test('complete customer signup and first order', async () => {
    const { page } = testEnv;

    // 1. Navigate to signup page
    await testEnv.navigateTo('/signup');
    
    // 2. Fill out customer form
    await page.fill('[data-testid="email"]', 'e2e-customer@example.com');
    await page.fill('[data-testid="first-name"]', 'John');
    await page.fill('[data-testid="last-name"]', 'Doe');
    await page.fill('[data-testid="phone"]', '+1-555-123-4567');

    // 3. Submit form and wait for API call
    const createCustomerPromise = testEnv.waitForApiCall('/v1/customers');
    await page.click('[data-testid="submit-button"]');
    await createCustomerPromise;

    // 4. Verify redirect to dashboard
    await expect(page).toHaveURL(/\/dashboard/);
    
    // 5. Check customer data is displayed
    await expect(page.locator('[data-testid="customer-name"]')).toContainText('John Doe');

    // 6. Navigate to products page
    await page.click('[data-testid="products-link"]');
    await expect(page).toHaveURL(/\/products/);

    // 7. Add product to cart
    await page.click('[data-testid="add-to-cart"]:first-of-type');
    
    // 8. Verify cart updates
    await expect(page.locator('[data-testid="cart-count"]')).toContainText('1');

    // 9. Go to checkout
    await page.click('[data-testid="cart-button"]');
    await page.click('[data-testid="checkout-button"]');

    // 10. Fill shipping address
    await page.fill('[data-testid="street1"]', '123 Test Street');
    await page.fill('[data-testid="city"]', 'Test City');
    await page.selectOption('[data-testid="state"]', 'CA');
    await page.fill('[data-testid="postal-code"]', '12345');

    // 11. Enter payment information (test mode)
    await page.fill('[data-testid="card-number"]', '4242424242424242');
    await page.fill('[data-testid="card-expiry"]', '12/25');
    await page.fill('[data-testid="card-cvc"]', '123');

    // 12. Submit order
    const createOrderPromise = testEnv.waitForApiCall('/v1/orders');
    await page.click('[data-testid="place-order-button"]');
    await createOrderPromise;

    // 13. Verify order confirmation
    await expect(page).toHaveURL(/\/order-confirmation/);
    await expect(page.locator('[data-testid="order-number"]')).toBeVisible();
    
    // 14. Extract order number for cleanup
    const orderNumber = await page.locator('[data-testid="order-number"]').textContent();
    console.log('Created order:', orderNumber);
  });

  test('customer support chat flow', async () => {
    const { page } = testEnv;

    // Create a customer first
    const customer = await testEnv.createTestCustomer();

    // 1. Navigate to support page with customer context
    await testEnv.navigateTo(`/support?customer_id=${customer.id}`);

    // 2. Start chat
    await page.click('[data-testid="start-chat-button"]');

    // 3. Send message
    await page.fill('[data-testid="chat-input"]', 'I need help with my order');
    await page.click('[data-testid="send-message"]');

    // 4. Wait for AI response
    await page.waitForSelector('[data-testid="ai-response"]', { timeout: 10000 });

    // 5. Verify response appears
    const response = await page.locator('[data-testid="ai-response"]').textContent();
    expect(response.length).toBeGreaterThan(0);

    // 6. Test escalation to human
    await page.fill('[data-testid="chat-input"]', 'I want to speak to a human');
    await page.click('[data-testid="send-message"]');

    // 7. Verify escalation UI appears
    await expect(page.locator('[data-testid="human-handoff"]')).toBeVisible();
  });

  test('return request flow', async () => {
    const { page } = testEnv;

    // Setup: Create customer and order
    const customer = await testEnv.createTestCustomer();
    
    // Navigate to returns page
    await testEnv.navigateTo('/returns/create');

    // 1. Enter order information
    await page.fill('[data-testid="order-number"]', 'ORD-TEST-123');
    await page.fill('[data-testid="email"]', customer.email);

    // 2. Click lookup order
    await page.click('[data-testid="lookup-order"]');

    // 3. Select items to return
    await page.check('[data-testid="return-item-0"]');

    // 4. Select reason
    await page.selectOption('[data-testid="return-reason"]', 'defective');

    // 5. Add description
    await page.fill('[data-testid="return-description"]', 'Product arrived damaged');

    // 6. Submit return request
    const createReturnPromise = testEnv.waitForApiCall('/v1/returns');
    await page.click('[data-testid="submit-return"]');
    await createReturnPromise;

    // 7. Verify return confirmation
    await expect(page.locator('[data-testid="return-number"]')).toBeVisible();
    await expect(page.locator('[data-testid="return-status"]')).toContainText('Requested');
  });
});

Performance Testing

Load Testing API Endpoints

// test/performance/loadTest.js
import { check, sleep } from 'k6';
import http from 'k6/http';

export let options = {
  stages: [
    { duration: '2m', target: 100 }, // Ramp up to 100 users
    { duration: '5m', target: 100 }, // Stay at 100 users
    { duration: '2m', target: 200 }, // Ramp up to 200 users
    { duration: '5m', target: 200 }, // Stay at 200 users
    { duration: '2m', target: 0 },   // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
    http_req_failed: ['rate<0.1'],    // Less than 10% error rate
  },
};

const API_BASE_URL = 'https://api.sandbox.stateset.com/v1';
const API_KEY = __ENV.STATESET_TEST_API_KEY;

export default function () {
  const headers = {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  };

  // Test customer creation
  const customerData = {
    email: `loadtest-${__VU}-${__ITER}@example.com`,
    first_name: 'Load',
    last_name: 'Test',
  };

  const createResponse = http.post(
    `${API_BASE_URL}/customers`,
    JSON.stringify(customerData),
    { headers }
  );

  check(createResponse, {
    'customer creation status is 201': (r) => r.status === 201,
    'customer creation response time < 500ms': (r) => r.timings.duration < 500,
  });

  if (createResponse.status === 201) {
    const customer = JSON.parse(createResponse.body);

    // Test customer retrieval
    const getResponse = http.get(
      `${API_BASE_URL}/customers/${customer.id}`,
      { headers }
    );

    check(getResponse, {
      'customer retrieval status is 200': (r) => r.status === 200,
      'customer retrieval response time < 200ms': (r) => r.timings.duration < 200,
    });

    // Test customer list
    const listResponse = http.get(
      `${API_BASE_URL}/customers?limit=10`,
      { headers }
    );

    check(listResponse, {
      'customer list status is 200': (r) => r.status === 200,
      'customer list response time < 300ms': (r) => r.timings.duration < 300,
    });
  }

  sleep(1);
}

Continuous Integration

GitHub Actions Workflow

# .github/workflows/api-tests.yml
name: API Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run unit tests
        run: npm run test:unit
        env:
          NODE_ENV: test
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run integration tests
        run: npm run test:integration
        env:
          STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}
          NODE_ENV: test
      
      - name: Upload test results
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: integration-test-results
          path: test-results/

  e2e-tests:
    runs-on: ubuntu-latest
    needs: integration-tests
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Install Playwright
        run: npx playwright install --with-deps
      
      - name: Start application
        run: |
          npm run build
          npm run start &
          sleep 10
        env:
          NODE_ENV: test
      
      - name: Run E2E tests
        run: npm run test:e2e
        env:
          STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}
          E2E_BASE_URL: http://localhost:3000
      
      - name: Upload E2E results
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: e2e-test-results
          path: test-results/

  performance-tests:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v3
      
      - name: Run k6 performance tests
        uses: grafana/k6-action@v0.3.0
        with:
          filename: test/performance/loadTest.js
        env:
          STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}

Test Data Management

Test Data Factory

// test/factories/testDataFactory.js
import { faker } from '@faker-js/faker';

export class TestDataFactory {
  static customer(overrides = {}) {
    return {
      email: faker.internet.email(),
      first_name: faker.person.firstName(),
      last_name: faker.person.lastName(),
      phone: faker.phone.number('+1-###-###-####'),
      date_of_birth: faker.date.birthdate({ min: 18, max: 80, mode: 'age' }).toISOString().split('T')[0],
      address: this.address(),
      customer_tier: faker.helpers.arrayElement(['bronze', 'silver', 'gold', 'platinum']),
      marketing_consent: faker.datatype.boolean(),
      ...overrides
    };
  }

  static address(overrides = {}) {
    return {
      street1: faker.location.streetAddress(),
      street2: faker.helpers.maybe(() => faker.location.secondaryAddress(), 0.3),
      city: faker.location.city(),
      state: faker.location.state({ abbreviated: true }),
      postal_code: faker.location.zipCode(),
      country: 'US',
      ...overrides
    };
  }

  static order(customerId, overrides = {}) {
    return {
      customer_id: customerId,
      items: [this.orderItem()],
      currency: 'USD',
      shipping_address: this.address(),
      billing_address: this.address(),
      ...overrides
    };
  }

  static orderItem(overrides = {}) {
    return {
      sku: faker.helpers.arrayElement(['WIDGET-001', 'GADGET-002', 'TOOL-003']),
      quantity: faker.number.int({ min: 1, max: 5 }),
      price: parseFloat(faker.commerce.price({ min: 10, max: 100, dec: 2 })),
      ...overrides
    };
  }

  static returnRequest(orderId, overrides = {}) {
    return {
      order_id: orderId,
      reason_code: faker.helpers.arrayElement(['defective', 'not_satisfied', 'wrong_item']),
      customer_notes: faker.lorem.sentence(),
      items: [this.returnItem()],
      ...overrides
    };
  }

  static returnItem(overrides = {}) {
    return {
      sku: faker.helpers.arrayElement(['WIDGET-001', 'GADGET-002', 'TOOL-003']),
      quantity: faker.number.int({ min: 1, max: 3 }),
      reason: faker.helpers.arrayElement(['defective', 'not_as_described', 'wrong_item']),
      condition: faker.helpers.arrayElement(['A', 'B', 'C']),
      ...overrides
    };
  }

  // Scenario-based data generation
  static scenarioData = {
    // High-value enterprise customer
    enterpriseCustomer: () => this.customer({
      customer_tier: 'platinum',
      custom_fields: {
        company_name: faker.company.name(),
        industry: 'Technology',
        company_size: '500+'
      }
    }),

    // Problem order for testing returns
    problematicOrder: (customerId) => this.order(customerId, {
      items: [
        this.orderItem({ sku: 'DEFECTIVE-ITEM', quantity: 1 })
      ]
    }),

    // Bulk order
    bulkOrder: (customerId) => this.order(customerId, {
      items: Array.from({ length: 5 }, () => this.orderItem())
    })
  };
}

Summary

Comprehensive API testing ensures:
  1. Unit Tests - Fast feedback on individual components
  2. Integration Tests - Verify API interactions work correctly
  3. Mock Tests - Test without external dependencies
  4. E2E Tests - Validate complete user workflows
  5. Performance Tests - Ensure system handles load
  6. CI/CD Integration - Automated testing in deployment pipeline
Testing Best Practices:
  • Write tests before implementing features (TDD)
  • Use descriptive test names that explain the scenario
  • Keep tests isolated and independent
  • Clean up test data after each test
  • Mock external dependencies for faster, more reliable tests
  • Monitor test performance and maintain fast feedback loops
This comprehensive testing approach helps ensure your StateSet API integration is robust, reliable, and ready for production.