Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Failing after 2m42s
479 lines
12 KiB
Markdown
479 lines
12 KiB
Markdown
---
|
|
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: ['<rootDir>/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. |