API Error Testing and Debugging

Test error paths and correlate client failures using timestamps, rate-limit headers, logs, and request IDs.

AuthOS release 0.8.2 API v1 Latest-only documentation
Updated Jul 15, 2026
On this page

Testing Error Scenarios

Unit Testing Error Handling

describe('API Error Handling', () => {
  it('should refresh token on TOKEN_EXPIRED', async () => {
    // Mock expired token response
    fetchMock.mockResponseOnce(
      JSON.stringify({
        error: 'Token expired',
        error_code: 'TOKEN_EXPIRED',
        timestamp: new Date().toISOString()
      }),
      { status: 401 }
    );

    // Mock successful refresh
    fetchMock.mockResponseOnce(
      JSON.stringify({
        access_token: 'new_token',
        refresh_token: 'new_refresh_token'
      })
    );

    // Mock successful retry
    fetchMock.mockResponseOnce(
      JSON.stringify({ id: 'user_123' })
    );

    const result = await apiRequestWithRefresh('/api/user');
    expect(result.id).toBe('user_123');
  });

  it('should handle rate limiting with backoff', async () => {
    // Mock rate limit response
    fetchMock.mockResponseOnce('', {
      status: 429,
      headers: { 'Retry-After': '2' }
    });

    // Mock successful retry
    fetchMock.mockResponseOnce(
      JSON.stringify({ users: [] })
    );

    const result = await fetchWithBackoff('/api/service/users');
    expect(result.users).toEqual([]);
  });
});

Integration Testing

Test error scenarios in integration tests:

describe('Service API Integration', () => {
  it('should return 403 for insufficient permissions', async () => {
    const apiKey = await createAPIKey(['read:users']); // No write permission

    const response = await fetch(`${API_URL}/api/service/users`, {
      method: 'POST',
      headers: {
        'X-API-Key': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ email: 'test@example.com' })
    });

    expect(response.status).toBe(403);
    const error = await response.json();
    expect(error.error_code).toBe('FORBIDDEN');
  });
});

Debugging Tips

1. Use Timestamps for Correlation

Error timestamps help correlate client-side errors with server logs:

const error = await response.json();
console.error(`Error at ${error.timestamp}:`, error.error);
// Search server logs around this timestamp

2. Check Rate Limit Headers

Monitor rate limit headers to prevent hitting limits:

const response = await fetch(url, options);
console.log('Rate Limit:', {
  limit: response.headers.get('X-RateLimit-Limit'),
  remaining: response.headers.get('X-RateLimit-Remaining'),
  reset: new Date(parseInt(response.headers.get('X-RateLimit-Reset')) * 1000)
});

3. Enable Request Logging

Log all API requests for debugging:

async function apiRequest(url, options) {
  const startTime = Date.now();
  console.log('API Request:', { url, method: options.method });

  try {
    const response = await fetch(url, options);
    const duration = Date.now() - startTime;

    console.log('API Response:', {
      url,
      status: response.status,
      duration: `${duration}ms`
    });

    return response;
  } catch (error) {
    const duration = Date.now() - startTime;
    console.error('API Request Failed:', {
      url,
      duration: `${duration}ms`,
      error: error.message
    });
    throw error;
  }
}

4. Use Request IDs

Generate unique request IDs for tracing:

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

async function apiRequest(url, options) {
  const requestId = generateRequestId();
  options.headers['X-Request-ID'] = requestId;

  console.log(`[${requestId}] Request:`, url);

  try {
    const response = await fetch(url, options);
    console.log(`[${requestId}] Response:`, response.status);
    return response;
  } catch (error) {
    console.error(`[${requestId}] Error:`, error.message);
    throw error;
  }
}