--- allowed-tools: Read, Write, Edit, Bash argument-hint: [route-path] [--method=GET] [--data='{}'] [--headers='{}'] description: Test and validate Next.js API routes with comprehensive test scenarios --- ## Next.js API Route Tester **API Route**: $ARGUMENTS ## Current Project Analysis ### API Routes Detection - App Router API: @app/api/ - Pages Router API: @pages/api/ - API configuration: @next.config.js - Environment variables: @.env.local ### Project Context - Next.js version: !`grep '"next"' package.json | head -1` - TypeScript config: @tsconfig.json (if exists) - Testing framework: @jest.config.js or @vitest.config.js (if exists) ## API Route Analysis ### Route Discovery Based on the provided route path, analyze: - **Route File**: Locate the actual route file - **HTTP Methods**: Supported methods (GET, POST, PUT, DELETE, PATCH) - **Route Parameters**: Dynamic segments and query parameters - **Middleware**: Applied middleware functions - **Authentication**: Required authentication/authorization ### Route Implementation Review - Route handler implementation: @app/api/[route-path]/route.ts or @pages/api/[route-path].ts - Type definitions: @types/ or inline types - Validation schemas: @lib/validations/ or inline validation - Database models: @lib/models/ or @models/ ## Test Generation Strategy ### 1. Basic Functionality Tests ```javascript // Basic API route test template describe('API Route: /api/[route-path]', () => { describe('GET requests', () => { test('should return 200 for valid request', async () => { const response = await fetch('/api/[route-path]'); expect(response.status).toBe(200); }); test('should return valid JSON response', async () => { const response = await fetch('/api/[route-path]'); const data = await response.json(); expect(data).toBeDefined(); expect(typeof data).toBe('object'); }); }); describe('POST requests', () => { test('should create resource with valid data', async () => { const testData = { name: 'Test', email: 'test@example.com' }; const response = await fetch('/api/[route-path]', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(testData) }); expect(response.status).toBe(201); const result = await response.json(); expect(result.name).toBe(testData.name); }); test('should reject invalid data', async () => { const invalidData = { invalid: 'field' }; const response = await fetch('/api/[route-path]', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(invalidData) }); expect(response.status).toBe(400); }); }); }); ``` ### 2. Authentication Tests ```javascript describe('Authentication', () => { test('should require authentication for protected routes', async () => { const response = await fetch('/api/protected-route'); expect(response.status).toBe(401); }); test('should allow authenticated requests', async () => { const token = 'valid-jwt-token'; const response = await fetch('/api/protected-route', { headers: { 'Authorization': `Bearer ${token}` } }); expect(response.status).not.toBe(401); }); test('should validate JWT token format', async () => { const invalidToken = 'invalid-token'; const response = await fetch('/api/protected-route', { headers: { 'Authorization': `Bearer ${invalidToken}` } }); expect(response.status).toBe(403); }); }); ``` ### 3. Input Validation Tests ```javascript describe('Input Validation', () => { const validationTests = [ { field: 'email', invalid: 'not-an-email', valid: 'test@example.com' }, { field: 'phone', invalid: '123', valid: '+1234567890' }, { field: 'age', invalid: -1, valid: 25 }, { field: 'name', invalid: '', valid: 'John Doe' } ]; validationTests.forEach(({ field, invalid, valid }) => { test(`should validate ${field} field`, async () => { const invalidData = { [field]: invalid }; const validData = { [field]: valid }; // Test invalid data const invalidResponse = await fetch('/api/[route-path]', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(invalidData) }); expect(invalidResponse.status).toBe(400); // Test valid data const validResponse = await fetch('/api/[route-path]', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(validData) }); expect(validResponse.status).not.toBe(400); }); }); }); ``` ### 4. Error Handling Tests ```javascript describe('Error Handling', () => { test('should handle malformed JSON', async () => { const response = await fetch('/api/[route-path]', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: 'invalid-json' }); expect(response.status).toBe(400); }); test('should handle missing Content-Type header', async () => { const response = await fetch('/api/[route-path]', { method: 'POST', body: JSON.stringify({ test: 'data' }) }); expect(response.status).toBe(400); }); test('should handle request timeout', async () => { // Mock slow endpoint jest.setTimeout(5000); const response = await fetch('/api/slow-endpoint'); // Test appropriate timeout handling }, 5000); test('should handle database connection errors', async () => { // Mock database failure const mockDbError = jest.spyOn(db, 'connect').mockRejectedValue(new Error('DB Error')); const response = await fetch('/api/[route-path]'); expect(response.status).toBe(500); mockDbError.mockRestore(); }); }); ``` ### 5. Performance Tests ```javascript describe('Performance', () => { test('should respond within acceptable time', async () => { const startTime = Date.now(); const response = await fetch('/api/[route-path]'); const endTime = Date.now(); expect(response.status).toBe(200); expect(endTime - startTime).toBeLessThan(1000); // 1 second }); test('should handle concurrent requests', async () => { const promises = Array.from({ length: 10 }, () => fetch('/api/[route-path]') ); const responses = await Promise.all(promises); responses.forEach(response => { expect(response.status).toBe(200); }); }); test('should implement rate limiting', async () => { const requests = Array.from({ length: 100 }, () => fetch('/api/[route-path]') ); const responses = await Promise.all(requests); const rateLimitedResponses = responses.filter(r => r.status === 429); expect(rateLimitedResponses.length).toBeGreaterThan(0); }); }); ``` ## Manual Testing Commands ### cURL Commands Generation ```bash # GET request curl -X GET "http://localhost:3000/api/[route-path]" \ -H "Accept: application/json" # POST request with data curl -X POST "http://localhost:3000/api/[route-path]" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"key": "value"}' # Authenticated request curl -X GET "http://localhost:3000/api/protected-route" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -H "Accept: application/json" # Upload file curl -X POST "http://localhost:3000/api/upload" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -F "file=@path/to/file.jpg" ``` ### HTTPie Commands ```bash # GET request http GET localhost:3000/api/[route-path] # POST request with JSON http POST localhost:3000/api/[route-path] key=value # Authenticated request http GET localhost:3000/api/protected-route Authorization:"Bearer TOKEN" # Custom headers http GET localhost:3000/api/[route-path] X-Custom-Header:value ``` ## Interactive Testing Tools ### Postman Collection Generation ```json { "info": { "name": "Next.js API Tests", "description": "Generated API tests for [route-path]" }, "item": [ { "name": "GET [route-path]", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/api/[route-path]", "host": ["{{baseUrl}}"], "path": ["api", "[route-path]"] } } }, { "name": "POST [route-path]", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"key\": \"value\"\n}" }, "url": { "raw": "{{baseUrl}}/api/[route-path]", "host": ["{{baseUrl}}"], "path": ["api", "[route-path]"] } } } ] } ``` ### Thunder Client Collection ```json { "client": "Thunder Client", "collectionName": "Next.js API Tests", "dateExported": "2024-01-01", "version": "1.1", "folders": [], "requests": [ { "name": "Test API Route", "url": "localhost:3000/api/[route-path]", "method": "GET", "headers": [ { "name": "Accept", "value": "application/json" } ] } ] } ``` ## Test Data Management ### Test Fixtures ```typescript // test/fixtures/apiTestData.ts export const validUserData = { name: 'John Doe', email: 'john@example.com', age: 30, role: 'user' }; export const invalidUserData = { name: '', email: 'invalid-email', age: -1, role: 'invalid-role' }; export const testHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'API-Test-Suite/1.0' }; ``` ### Mock Data Generation ```typescript // test/utils/mockData.ts export function generateMockUser() { return { id: Math.random().toString(36).substr(2, 9), name: `User ${Math.floor(Math.random() * 1000)}`, email: `user${Date.now()}@example.com`, createdAt: new Date().toISOString() }; } export function generateBulkTestData(count: number) { return Array.from({ length: count }, generateMockUser); } ``` ## Test Environment Setup ### Jest Configuration ```javascript // jest.config.js for API testing module.exports = { testEnvironment: 'node', setupFilesAfterEnv: ['/test/setup.js'], testMatch: ['**/__tests__/**/*.test.js', '**/?(*.)+(spec|test).js'], collectCoverageFrom: [ 'pages/api/**/*.{js,ts}', 'app/api/**/*.{js,ts}', '!**/*.d.ts', ], coverageThreshold: { global: { branches: 70, functions: 70, lines: 70, statements: 70 } } }; ``` ### Test Setup ```javascript // test/setup.js import { createMocks } from 'node-mocks-http'; import { testDb } from './testDatabase'; // Global test setup beforeAll(async () => { // Setup test database await testDb.connect(); }); afterAll(async () => { // Cleanup test database await testDb.disconnect(); }); beforeEach(async () => { // Reset database state await testDb.reset(); }); // Helper function for API testing global.createAPITest = (handler) => { return (method, url, options = {}) => { const { req, res } = createMocks({ method, url, ...options }); return handler(req, res); }; }; ``` ## Automated Testing Integration ### GitHub Actions Workflow ```yaml name: API Tests on: [push, pull_request] jobs: test-api: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' - run: npm ci - run: npm run test:api - name: Upload coverage uses: codecov/codecov-action@v3 ``` ### Continuous Testing ```bash # Watch mode for development npm run test:api -- --watch # Coverage reporting npm run test:api -- --coverage # Specific route testing npm run test:api -- --testNamePattern="api/users" ``` ## Test Results Analysis Generate comprehensive test report including: 1. **Test Coverage**: Line, branch, function coverage percentages 2. **Performance Metrics**: Response times, throughput 3. **Security Analysis**: Authentication, authorization, input validation 4. **Error Handling**: Exception scenarios and error responses 5. **Compatibility**: Cross-environment testing results Provide actionable recommendations for improving API reliability, performance, and security.