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:Copy
Base URL: https://ts.playservices.tech/api
WebSocket: wss://ts.playservices.tech/ws
Documentation: https://ts.playservices.tech/docs
Test Credentials
Copy
{
"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:Copy
// 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
Copy
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
Copy
// 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
Copy
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
Copy
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 responsesCopy
// 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;
};
- 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 RequestsCopy
// 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 receivedCopy
// 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 failuresCopy
// 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
Copy
// 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
Copy
// 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
Copy
// 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');
}
};

