Skip to main content

Testing & Debugging

This guide provides comprehensive testing strategies, debugging tools, and troubleshooting techniques to ensure reliable integration with the Tournament Platform.

Testing Environment Setup

Production Environment

Use the production Tournament Platform API for testing and development: Production Endpoints:
Base URL: https://ts.playservices.tech/api
WebSocket: wss://ts.playservices.tech/ws
Documentation: https://ts.playservices.tech/docs

Test Credentials

{
  "operatorId": "op_test_1234567890",
  "apiKey": "sk_live_test_abcdef123456789",
  "testPlayerPrefix": "test_player_",
  "defaultCurrency": "USD",
  "environment": "production"
}

Test Data Creation

Create test tournaments and players for integration testing:
// Create test tournament
const createTestTournament = async () => {
  const response = await fetch('https://ts.playservices.tech/api/tournaments', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_test_abcdef123456789',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Test Tournament - ' + new Date().toISOString(),
      gameSlug: 'crash-classic',
      scheduledStart: new Date(Date.now() + 300000).toISOString(), // 5 minutes from now
      maxPlayers: 10,
      entryFee: 1.00,
      currency: 'USD',
      gameConfig: {
        maxRounds: 3,
        roundDuration: 180,
        eliminationRate: 0.5
      },
      prizeDistribution: [70, 20, 10],
      rebuyConfig: {
        enabled: true,
        maxRebuys: 2,
        rebuyFee: 1.00
      }
    })
  });
  
  const tournament = await response.json();
  console.log('Test tournament created:', tournament.tournamentId);
  return tournament;
};

// Register test player
const registerTestPlayer = async (tournamentId, playerData) => {
  const response = await fetch(`https://ts.playservices.tech/api/tournaments/${tournamentId}/players`, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_test_abcdef123456789',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      playerId: `test_player_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
      displayName: playerData.displayName || 'Test Player',
      entryFee: 1.00,
      currency: 'USD',
      playerMetadata: {
        isTestPlayer: true,
        testScenario: playerData.scenario || 'basic'
      }
    })
  });
  
  const registration = await response.json();
  console.log('Test player registered:', registration.playerId);
  return registration;
};

Integration Testing Framework

API Testing Suite

const assert = require('assert');

class TournamentAPITest {
  constructor(apiKey, baseUrl) {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.testResults = [];
  }
  
  async runAllTests() {
    console.log('Starting Tournament API integration tests...');
    
    const tests = [
      { name: 'Tournament Creation', fn: this.testTournamentCreation },
      { name: 'Player Registration', fn: this.testPlayerRegistration },
      { name: 'Player Rebuy', fn: this.testPlayerRebuy },
      { name: 'Tournament Cancellation', fn: this.testTournamentCancellation },
      { name: 'Results Retrieval', fn: this.testResultsRetrieval },
      { name: 'Webhook Delivery', fn: this.testWebhookDelivery },
      { name: 'Error Handling', fn: this.testErrorHandling }
    ];
    
    for (const test of tests) {
      try {
        console.log(`Running test: ${test.name}`);
        await test.fn.call(this);
        this.testResults.push({ name: test.name, status: 'PASS' });
        console.log(` ${test.name} PASSED`);
      } catch (error) {
        this.testResults.push({ 
          name: test.name, 
          status: 'FAIL', 
          error: error.message 
        });
        console.error(`L ${test.name} FAILED:`, error.message);
      }
    }
    
    this.generateTestReport();
  }
  
  async testTournamentCreation() {
    const tournament = await this.createTestTournament({
      name: 'API Test Tournament',
      gameSlug: 'crash-classic',
      maxPlayers: 5,
      entryFee: 2.00
    });
    
    assert(tournament.tournamentId, 'Tournament ID should be present');
    assert(tournament.status === 'scheduled', 'Tournament should be scheduled');
    assert(tournament.maxPlayers === 5, 'Max players should match input');
    assert(tournament.entryFee === 2.00, 'Entry fee should match input');
    
    return tournament;
  }
  
  async testPlayerRegistration() {
    const tournament = await this.createTestTournament();
    
    const registration = await this.registerTestPlayer(tournament.tournamentId, {
      displayName: 'API Test Player',
      entryFee: tournament.entryFee
    });
    
    assert(registration.playerId, 'Player ID should be present');
    assert(registration.jwtToken, 'JWT token should be provided');
    assert(registration.gameUrl, 'Game URL should be provided');
    assert(registration.status === 'registered', 'Player should be registered');
    
    return registration;
  }
  
  async testPlayerRebuy() {
    const tournament = await this.createTestTournament();
    const registration = await this.registerTestPlayer(tournament.tournamentId);
    
    const rebuy = await this.processRebuy(tournament.tournamentId, registration.playerId);
    
    assert(rebuy.rebuyProcessed === true, 'Rebuy should be processed');
    assert(rebuy.newEntryCount === 2, 'Entry count should be updated');
    assert(rebuy.remainingRebuys >= 0, 'Remaining rebuys should be valid');
    
    return rebuy;
  }
  
  async testTournamentCancellation() {
    const tournament = await this.createTestTournament();
    
    const cancellation = await this.cancelTournament(tournament.tournamentId, 'test_cancellation');
    
    assert(cancellation.cancelled === true, 'Tournament should be cancelled');
    assert(cancellation.reason === 'test_cancellation', 'Cancellation reason should match');
    
    return cancellation;
  }
  
  async testResultsRetrieval() {
    // Use completed test tournament
    const completedTournamentId = 'test_completed_tournament_123';
    
    const results = await this.getTournamentResults(completedTournamentId);
    
    assert(results.tournamentId === completedTournamentId, 'Tournament ID should match');
    assert(results.status === 'completed', 'Tournament should be completed');
    assert(Array.isArray(results.winners), 'Winners should be an array');
    assert(results.winners.length > 0, 'Should have at least one winner');
    
    return results;
  }
  
  async testWebhookDelivery() {
    // Test webhook endpoint
    const webhook = await this.createTestWebhook();
    
    const test = await this.testWebhookEndpoint(webhook.webhookId);
    
    assert(test.deliveryStatus === 'success', 'Webhook should deliver successfully');
    assert(test.httpStatus === 200, 'Should receive 200 OK response');
    
    return test;
  }
  
  async testErrorHandling() {
    // Test various error scenarios
    const errorTests = [
      { name: 'Invalid API Key', test: this.testInvalidAPIKey },
      { name: 'Tournament Not Found', test: this.testTournamentNotFound },
      { name: 'Invalid Player Data', test: this.testInvalidPlayerData },
      { name: 'Rate Limiting', test: this.testRateLimiting }
    ];
    
    for (const errorTest of errorTests) {
      try {
        await errorTest.test.call(this);
        throw new Error(`Expected ${errorTest.name} to throw an error`);
      } catch (error) {
        // Expected error - verify error format
        assert(error.message || error.error, 'Error should have message');
      }
    }
  }
  
  // Helper methods
  async createTestTournament(config = {}) {
    const response = await fetch(`${this.baseUrl}/api/tournaments`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'Test Tournament',
        gameSlug: 'crash-classic',
        scheduledStart: new Date(Date.now() + 300000).toISOString(),
        maxPlayers: 10,
        entryFee: 1.00,
        currency: 'USD',
        ...config
      })
    });
    
    if (!response.ok) {
      throw new Error(`Tournament creation failed: ${response.status}`);
    }
    
    return await response.json();
  }
  
  generateTestReport() {
    const passed = this.testResults.filter(r => r.status === 'PASS').length;
    const failed = this.testResults.filter(r => r.status === 'FAIL').length;
    const total = this.testResults.length;
    
    console.log('\n=== Test Report ===');
    console.log(`Total Tests: ${total}`);
    console.log(`Passed: ${passed}`);
    console.log(`Failed: ${failed}`);
    console.log(`Success Rate: ${((passed / total) * 100).toFixed(1)}%`);
    
    if (failed > 0) {
      console.log('\nFailed Tests:');
      this.testResults
        .filter(r => r.status === 'FAIL')
        .forEach(r => console.log(`- ${r.name}: ${r.error}`));
    }
    
    return {
      total,
      passed,
      failed,
      successRate: (passed / total) * 100,
      results: this.testResults
    };
  }
}

// Usage
const testSuite = new TournamentAPITest(
  'sk_live_test_abcdef123456789',
  'https://ts.playservices.tech/api'
);

testSuite.runAllTests();

Load Testing

// Load testing with multiple concurrent requests
const loadTest = async (concurrent = 10, requests = 100) => {
  const apiKey = 'sk_live_test_abcdef123456789';
  const baseUrl = 'https://ts.playservices.tech/api';
  
  console.log(`Starting load test: ${concurrent} concurrent users, ${requests} requests`);
  
  const startTime = Date.now();
  const results = [];
  
  // Create concurrent workers
  const workers = Array.from({ length: concurrent }, async (_, workerId) => {
    const requestsPerWorker = Math.floor(requests / concurrent);
    
    for (let i = 0; i < requestsPerWorker; i++) {
      const requestStart = Date.now();
      
      try {
        const response = await fetch(`${baseUrl}/api/tournaments`, {
          headers: { 'Authorization': `Bearer ${apiKey}` }
        });
        
        const duration = Date.now() - requestStart;
        results.push({
          workerId,
          requestIndex: i,
          status: response.status,
          duration,
          success: response.ok
        });
        
      } catch (error) {
        results.push({
          workerId,
          requestIndex: i,
          error: error.message,
          duration: Date.now() - requestStart,
          success: false
        });
      }
    }
  });
  
  // Wait for all workers to complete
  await Promise.all(workers);
  
  const totalTime = Date.now() - startTime;
  const successful = results.filter(r => r.success).length;
  const failed = results.length - successful;
  const averageLatency = results.reduce((sum, r) => sum + r.duration, 0) / results.length;
  
  console.log('\n=== Load Test Results ===');
  console.log(`Total Requests: ${results.length}`);
  console.log(`Successful: ${successful}`);
  console.log(`Failed: ${failed}`);
  console.log(`Success Rate: ${((successful / results.length) * 100).toFixed(1)}%`);
  console.log(`Total Time: ${totalTime}ms`);
  console.log(`Average Latency: ${averageLatency.toFixed(0)}ms`);
  console.log(`Requests/Second: ${(results.length / (totalTime / 1000)).toFixed(2)}`);
  
  return {
    totalRequests: results.length,
    successful,
    failed,
    successRate: (successful / results.length) * 100,
    totalTime,
    averageLatency,
    requestsPerSecond: results.length / (totalTime / 1000),
    results
  };
};

// Run load test
loadTest(5, 50);

Debugging Tools

API Request Inspector

class APIInspector {
  constructor(apiKey, baseUrl) {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.requestLog = [];
  }
  
  async inspectRequest(method, endpoint, data = null) {
    const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
    const requestTime = new Date();
    
    console.log(`\n[${requestId}] ${method} ${endpoint}`);
    console.log('Headers:', {
      'Authorization': `Bearer ${this.apiKey.substr(0, 20)}...`,
      'Content-Type': 'application/json'
    });
    
    if (data) {
      console.log('Body:', JSON.stringify(data, null, 2));
    }
    
    const startTime = Date.now();
    
    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`, {
        method,
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        },
        body: data ? JSON.stringify(data) : undefined
      });
      
      const duration = Date.now() - startTime;
      const responseBody = await response.text();
      
      let parsedBody;
      try {
        parsedBody = JSON.parse(responseBody);
      } catch {
        parsedBody = responseBody;
      }
      
      // Log response
      console.log(`\n[${requestId}] Response (${duration}ms):`);
      console.log('Status:', response.status, response.statusText);
      console.log('Headers:', Object.fromEntries(response.headers.entries()));
      console.log('Body:', typeof parsedBody === 'object' 
        ? JSON.stringify(parsedBody, null, 2) 
        : parsedBody
      );
      
      // Store in log
      this.requestLog.push({
        requestId,
        timestamp: requestTime,
        method,
        endpoint,
        requestData: data,
        responseStatus: response.status,
        responseData: parsedBody,
        duration,
        success: response.ok
      });
      
      return {
        status: response.status,
        headers: Object.fromEntries(response.headers.entries()),
        data: parsedBody,
        duration
      };
      
    } catch (error) {
      const duration = Date.now() - startTime;
      
      console.error(`\n[${requestId}] Error (${duration}ms):`, error.message);
      
      this.requestLog.push({
        requestId,
        timestamp: requestTime,
        method,
        endpoint,
        requestData: data,
        error: error.message,
        duration,
        success: false
      });
      
      throw error;
    }
  }
  
  exportRequestLog(filename = 'api-requests.json') {
    const fs = require('fs');
    fs.writeFileSync(filename, JSON.stringify(this.requestLog, null, 2));
    console.log(`Request log exported to ${filename}`);
  }
  
  analyzePerformance() {
    const successful = this.requestLog.filter(r => r.success);
    const failed = this.requestLog.filter(r => !r.success);
    
    if (successful.length === 0) {
      console.log('No successful requests to analyze');
      return;
    }
    
    const avgLatency = successful.reduce((sum, r) => sum + r.duration, 0) / successful.length;
    const minLatency = Math.min(...successful.map(r => r.duration));
    const maxLatency = Math.max(...successful.map(r => r.duration));
    
    console.log('\n=== Performance Analysis ===');
    console.log(`Total Requests: ${this.requestLog.length}`);
    console.log(`Successful: ${successful.length}`);
    console.log(`Failed: ${failed.length}`);
    console.log(`Average Latency: ${avgLatency.toFixed(0)}ms`);
    console.log(`Min Latency: ${minLatency}ms`);
    console.log(`Max Latency: ${maxLatency}ms`);
    
    // Group by endpoint
    const endpointStats = {};
    successful.forEach(req => {
      if (!endpointStats[req.endpoint]) {
        endpointStats[req.endpoint] = [];
      }
      endpointStats[req.endpoint].push(req.duration);
    });
    
    console.log('\nEndpoint Performance:');
    Object.entries(endpointStats).forEach(([endpoint, durations]) => {
      const avg = durations.reduce((sum, d) => sum + d, 0) / durations.length;
      console.log(`  ${endpoint}: ${avg.toFixed(0)}ms avg (${durations.length} requests)`);
    });
  }
}

// Usage
const inspector = new APIInspector(
  'sk_live_test_abcdef123456789',
  'https://ts.playservices.tech/api'
);

// Inspect tournament creation
inspector.inspectRequest('POST', '/api/tournaments', {
  name: 'Debug Tournament',
  gameSlug: 'crash-classic',
  maxPlayers: 10
});

// Inspect tournament list
inspector.inspectRequest('GET', '/api/tournaments?status=scheduled&limit=5');

// Analyze performance after tests
inspector.analyzePerformance();
inspector.exportRequestLog('debug-session.json');

Webhook Testing Tool

const express = require('express');
const crypto = require('crypto');

class WebhookTester {
  constructor(port = 3001) {
    this.app = express();
    this.port = port;
    this.receivedWebhooks = [];
    this.webhookSecret = 'test-webhook-secret-123';
    
    this.setupRoutes();
  }
  
  setupRoutes() {
    this.app.use(express.json());
    
    // Test webhook endpoint
    this.app.post('/webhook-test', (req, res) => {
      const signature = req.headers['x-tournament-signature'];
      const timestamp = new Date().toISOString();
      const eventData = req.body;
      
      console.log(`\n[${timestamp}] Webhook received:`);
      console.log('Headers:', req.headers);
      console.log('Body:', JSON.stringify(eventData, null, 2));
      
      // Verify signature
      const isValidSignature = this.verifySignature(
        JSON.stringify(eventData),
        signature
      );
      
      console.log('Signature Valid:', isValidSignature);
      
      // Store webhook data
      this.receivedWebhooks.push({
        timestamp,
        eventType: eventData.eventType,
        eventId: eventData.eventId,
        tournamentId: eventData.tournamentId,
        signature,
        signatureValid: isValidSignature,
        data: eventData,
        headers: req.headers
      });
      
      // Simulate processing delay
      setTimeout(() => {
        if (this.shouldSimulateFailure(eventData)) {
          console.log('Simulating webhook failure');
          res.status(500).json({ error: 'Simulated failure' });
        } else {
          res.status(200).json({ 
            received: true,
            eventId: eventData.eventId,
            processedAt: new Date().toISOString()
          });
        }
      }, 100);
    });
    
    // Webhook stats endpoint
    this.app.get('/webhook-stats', (req, res) => {
      const stats = this.getWebhookStats();
      res.json(stats);
    });
    
    // Clear webhook history
    this.app.delete('/webhook-history', (req, res) => {
      this.receivedWebhooks = [];
      res.json({ cleared: true });
    });
    
    // Get webhook history
    this.app.get('/webhook-history', (req, res) => {
      res.json({
        webhooks: this.receivedWebhooks,
        count: this.receivedWebhooks.length
      });
    });
  }
  
  verifySignature(body, signature) {
    if (!signature) return false;
    
    const expectedSignature = crypto
      .createHmac('sha256', this.webhookSecret)
      .update(body)
      .digest('hex');
    
    const receivedSignature = signature.replace('sha256=', '');
    
    return crypto.timingSafeEqual(
      Buffer.from(expectedSignature, 'hex'),
      Buffer.from(receivedSignature, 'hex')
    );
  }
  
  shouldSimulateFailure(eventData) {
    // Simulate 5% failure rate for testing
    return Math.random() < 0.05;
  }
  
  getWebhookStats() {
    const total = this.receivedWebhooks.length;
    const validSignatures = this.receivedWebhooks.filter(w => w.signatureValid).length;
    const eventTypes = {};
    const tournaments = {};
    
    this.receivedWebhooks.forEach(webhook => {
      // Count by event type
      eventTypes[webhook.eventType] = (eventTypes[webhook.eventType] || 0) + 1;
      
      // Count by tournament
      if (webhook.tournamentId) {
        tournaments[webhook.tournamentId] = (tournaments[webhook.tournamentId] || 0) + 1;
      }
    });
    
    return {
      totalWebhooks: total,
      validSignatures,
      signatureSuccessRate: total > 0 ? (validSignatures / total * 100).toFixed(1) + '%' : '0%',
      eventTypeBreakdown: eventTypes,
      tournamentBreakdown: tournaments,
      recentWebhooks: this.receivedWebhooks.slice(-10)
    };
  }
  
  start() {
    return new Promise((resolve) => {
      this.server = this.app.listen(this.port, () => {
        console.log(`\nWebhook tester running on http://localhost:${this.port}`);
        console.log(`Test endpoint: http://localhost:${this.port}/webhook-test`);
        console.log(`Stats endpoint: http://localhost:${this.port}/webhook-stats`);
        resolve();
      });
    });
  }
  
  stop() {
    if (this.server) {
      this.server.close();
    }
  }
}

// Usage
const webhookTester = new WebhookTester(3001);
webhookTester.start();

// Register this URL with Tournament Platform:
// https://your-domain.com:3001/webhook-test

Troubleshooting Guide

Common Issues & Solutions

Authentication Errors

Issue: 401 Unauthorized responses
// Debug authentication
const debugAuth = async (apiKey, endpoint) => {
  const response = await fetch(endpoint, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });
  
  if (response.status === 401) {
    console.error('Authentication failed:');
    console.log('- Check API key format (should start with "sk_" for live, "sk_test_" for sandbox)');
    console.log('- Verify API key is active in dashboard');
    console.log('- Ensure using correct environment (sandbox vs production)');
    console.log('- Check for extra whitespace in API key');
    
    const authHeader = `Bearer ${apiKey}`;
    console.log('Full auth header:', authHeader);
    console.log('API key length:', apiKey.length);
  }
  
  return response;
};
Solutions:
  • Verify API key format and environment
  • Check API key permissions in dashboard
  • Ensure no whitespace in API key
  • Confirm correct base URL for environment

Rate Limiting

Issue: 429 Too Many Requests
// Implement exponential backoff
const apiCallWithBackoff = async (apiCall, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await apiCall();
      
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
        console.log(`Rate limited, retrying in ${retryAfter}s (attempt ${attempt}/${maxRetries})`);
        
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }
      
      return response;
      
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Request failed, retrying in ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
};

Webhook Delivery Issues

Issue: Webhooks not being received
// Webhook diagnostics
const diagnoseWebhookIssues = async (webhookId, apiKey) => {
  console.log('Diagnosing webhook issues...');
  
  // Check webhook configuration
  const config = await fetch(`/api/webhooks/${webhookId}`, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  }).then(r => r.json());
  
  console.log('Webhook config:', config);
  
  // Check recent deliveries
  const deliveries = await fetch(`/api/webhooks/${webhookId}/deliveries?limit=10`, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  }).then(r => r.json());
  
  console.log('Recent deliveries:', deliveries);
  
  // Test webhook endpoint
  const testResult = await fetch(`/api/webhooks/${webhookId}/test`, {
    method: 'POST',
    headers: { 
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      eventType: 'test.webhook',
      data: { test: true }
    })
  }).then(r => r.json());
  
  console.log('Test result:', testResult);
  
  // Provide recommendations
  if (config.active === false) {
    console.log('� Webhook is disabled - enable it in the configuration');
  }
  
  if (deliveries.deliveries?.some(d => !d.success)) {
    console.log('� Some deliveries failed - check your endpoint response status');
  }
  
  if (testResult.deliveryStatus !== 'success') {
    console.log('� Test delivery failed - verify your endpoint is accessible');
  }
};

JWT Token Issues

Issue: Token validation failures
// Debug JWT tokens
const debugJWTToken = (token) => {
  try {
    const parts = token.split('.');
    if (parts.length !== 3) {
      throw new Error('Invalid JWT format');
    }
    
    const header = JSON.parse(atob(parts[0]));
    const payload = JSON.parse(atob(parts[1]));
    
    console.log('JWT Header:', header);
    console.log('JWT Payload:', payload);
    
    // Check expiration
    if (payload.exp) {
      const expiration = new Date(payload.exp * 1000);
      const now = new Date();
      
      console.log('Expires at:', expiration.toISOString());
      console.log('Current time:', now.toISOString());
      console.log('Is expired:', expiration < now);
      
      if (expiration < now) {
        console.error('Token has expired - request new token from casino system');
      }
    }
    
    // Check required claims
    const requiredClaims = ['tournamentId', 'playerId', 'permissions'];
    const missingClaims = requiredClaims.filter(claim => !payload[claim]);
    
    if (missingClaims.length > 0) {
      console.error('Missing required claims:', missingClaims);
    }
    
  } catch (error) {
    console.error('JWT parsing failed:', error.message);
    console.log('Token might be malformed or corrupted');
  }
};

Performance Optimization

Request Batching

// Batch multiple API calls
class APIBatcher {
  constructor(apiKey, baseUrl) {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.batchQueue = [];
    this.batchTimeout = null;
    this.batchSize = 10;
    this.batchDelay = 100; // ms
  }
  
  queueRequest(method, endpoint, data = null) {
    return new Promise((resolve, reject) => {
      this.batchQueue.push({
        method,
        endpoint,
        data,
        resolve,
        reject
      });
      
      if (this.batchQueue.length >= this.batchSize) {
        this.flushBatch();
      } else if (!this.batchTimeout) {
        this.batchTimeout = setTimeout(() => {
          this.flushBatch();
        }, this.batchDelay);
      }
    });
  }
  
  async flushBatch() {
    if (this.batchTimeout) {
      clearTimeout(this.batchTimeout);
      this.batchTimeout = null;
    }
    
    if (this.batchQueue.length === 0) return;
    
    const batch = this.batchQueue.splice(0, this.batchSize);
    
    // Execute all requests concurrently
    const promises = batch.map(async (request) => {
      try {
        const response = await fetch(`${this.baseUrl}${request.endpoint}`, {
          method: request.method,
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Type': 'application/json'
          },
          body: request.data ? JSON.stringify(request.data) : undefined
        });
        
        const result = await response.json();
        request.resolve(result);
        
      } catch (error) {
        request.reject(error);
      }
    });
    
    await Promise.allSettled(promises);
    
    // Process remaining queue
    if (this.batchQueue.length > 0) {
      setTimeout(() => this.flushBatch(), this.batchDelay);
    }
  }
}

Caching Strategy

// API response caching
class APICache {
  constructor(ttl = 300000) { // 5 minutes default
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  generateKey(method, endpoint, data) {
    const payload = data ? JSON.stringify(data) : '';
    return `${method}:${endpoint}:${crypto.createHash('md5').update(payload).digest('hex')}`;
  }
  
  get(method, endpoint, data = null) {
    const key = this.generateKey(method, endpoint, data);
    const cached = this.cache.get(key);
    
    if (cached && (Date.now() - cached.timestamp < this.ttl)) {
      console.log('Cache hit:', key);
      return cached.data;
    }
    
    return null;
  }
  
  set(method, endpoint, data, response) {
    const key = this.generateKey(method, endpoint, data);
    this.cache.set(key, {
      data: response,
      timestamp: Date.now()
    });
    
    console.log('Cache set:', key);
  }
  
  clear() {
    this.cache.clear();
    console.log('Cache cleared');
  }
  
  cleanup() {
    const now = Date.now();
    for (const [key, entry] of this.cache.entries()) {
      if (now - entry.timestamp > this.ttl) {
        this.cache.delete(key);
      }
    }
  }
}

// Usage with API calls
const apiCache = new APICache();

const cachedAPICall = async (method, endpoint, data = null) => {
  // Check cache first
  const cached = apiCache.get(method, endpoint, data);
  if (cached) {
    return cached;
  }
  
  // Make API call
  const response = await fetch(`${baseUrl}${endpoint}`, {
    method,
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: data ? JSON.stringify(data) : undefined
  });
  
  const result = await response.json();
  
  // Cache successful responses
  if (response.ok) {
    apiCache.set(method, endpoint, data, result);
  }
  
  return result;
};

Best Practices

Error Handling

Retry Logic

Implement exponential backoff for transient failures and rate limiting

Graceful Degradation

Handle API failures gracefully with fallback behavior

Monitoring

Log and monitor API response times and error rates

Circuit Breaker

Implement circuit breaker pattern for service protection

Security Testing

// Security test checklist
const securityTests = {
  async testAPIKeyValidation() {
    // Test with invalid API key
    const response = await fetch('/api/tournaments', {
      headers: { 'Authorization': 'Bearer invalid_key' }
    });
    
    assert(response.status === 401, 'Should reject invalid API key');
  },
  
  async testInputSanitization() {
    // Test SQL injection attempts
    const maliciousInput = "'; DROP TABLE tournaments; --";
    
    const response = await fetch('/api/tournaments', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: maliciousInput
      })
    });
    
    // Should either sanitize input or reject request
    assert(response.status !== 200 || !response.error, 'Should handle malicious input');
  },
  
  async testRateLimiting() {
    // Test rate limiting
    const requests = Array.from({ length: 100 }, () => 
      fetch('/api/tournaments', {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      })
    );
    
    const responses = await Promise.all(requests);
    const rateLimited = responses.some(r => r.status === 429);
    
    assert(rateLimited, 'Should enforce rate limits');
  }
};

Next Steps