API Test Suite Builder
Tier: POWERFUL Category: Engineering / Testing Maintainer: Claude Skills Team
Overview
Scan API route definitions across frameworks (Next.js App Router, Express, FastAPI, Django REST, Go net/http), analyze request/response schemas, and generate comprehensive test suites covering authentication, authorization, input validation, error handling, pagination, file uploads, rate limiting, contract testing, and load testing. Outputs ready-to-run test files for Vitest+Supertest (Node), Pytest+httpx (Python), or k6 (load testing).
Keywords
API testing, test generation, contract testing, load testing, Pact, k6, Supertest, httpx, auth testing, input validation, error matrix, OpenAPI testing, regression suite
Core Capabilities
1. Route Detection and Analysis
- Scan source files to extract all API endpoints with HTTP methods
- Parse request body schemas from types, validators, and decorators
- Detect authentication middleware and authorization rules
- Identify response types and status codes from handler implementations
2. Test Matrix Generation
- Authentication: valid/invalid/expired tokens, missing headers, wrong roles
- Input validation: missing fields, wrong types, boundary values, injection
- Error paths: 400/401/403/404/409/422/429/500 for each route
- Pagination: first/last/empty/oversized pages, cursor-based and offset
- File uploads: valid, oversized, wrong MIME, empty, path traversal
- Rate limiting: burst detection, per-user vs global limits
3. Contract Testing
- OpenAPI spec to test generation
- Pact consumer-driven contract tests
- Schema snapshot testing for breaking change detection
- Response shape validation with JSON Schema
4. Load Testing
- k6 scripts with ramp-up patterns and SLA thresholds
- Artillery scenarios for sustained load profiles
- Latency percentile tracking (P50, P95, P99)
- Concurrent user simulation with realistic data
When to Use
- New API added — generate test scaffold before implementation (TDD)
- Legacy API with no tests — scan and generate baseline coverage
- Pre-release — ensure all routes have at least smoke tests
- API contract change — detect and test breaking changes
- Security audit — generate adversarial input tests
- Performance validation — create load test baselines
Route Detection Commands
Next.js App Router
# Find all route handlers and extract HTTP methods
find ./app/api -name "route.ts" -o -name "route.js" | while read f; do
route=$(echo "$f" | sed 's|./app||; s|/route\.[tj]s||')
methods=$(grep -oE "export (async )?function (GET|POST|PUT|PATCH|DELETE)" "$f" | \
grep -oE "(GET|POST|PUT|PATCH|DELETE)" | tr '\n' ',')
echo "$methods $route"
done
Express / Fastify
grep -rn "router\.\(get\|post\|put\|delete\|patch\)\|app\.\(get\|post\|put\|delete\|patch\)" \
src/ --include="*.ts" --include="*.js" | \
grep -oE "\.(get|post|put|delete|patch)\(['\"][^'\"]+['\"]" | \
sed "s/\.\(.*\)('\(.*\)'/\U\1 \2/"
FastAPI
grep -rn "@\(app\|router\)\.\(get\|post\|put\|delete\|patch\)" . --include="*.py" | \
grep -oE "(get|post|put|delete|patch)\(['\"][^'\"]*['\"]"
Go (net/http, Chi, Gin)
grep -rn "\.HandleFunc\|\.Handle\|\.GET\|\.POST\|\.PUT\|\.DELETE" . --include="*.go" | \
grep -oE "(GET|POST|PUT|DELETE|HandleFunc)\(['\"][^'\"]*['\"]"
Test Generation Framework
Auth Test Matrix
For every authenticated endpoint, generate these test cases:
// tests/api/[resource]/auth.test.ts
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import { createTestApp } from '../../helpers/app'
import { createTestUser, generateToken, generateExpiredToken } from '../../helpers/auth'
describe('GET /api/v1/projects - Authentication', () => {
const app = createTestApp()
it('returns 401 when no Authorization header is sent', async () => {
const res = await request(app).get('/api/v1/projects')
expect(res.status).toBe(401)
expect(res.body.error).toMatchObject({
code: 'UNAUTHORIZED',
message: expect.any(String),
})
})
it('returns 401 when token format is invalid', async () => {
const res = await request(app)
.get('/api/v1/projects')
.set('Authorization', 'InvalidFormat')
expect(res.status).toBe(401)
})
it('returns 401 when token is expired', async () => {
const token = generateExpiredToken({ userId: 'user_123' })
const res = await request(app)
.get('/api/v1/projects')
.set('Authorization', `Bearer ${token}`)
expect(res.status).toBe(401)
expect(res.body.error.code).toBe('TOKEN_EXPIRED')
})
it('returns 403 when user lacks required role', async () => {
const user = await createTestUser({ role: 'viewer' })
const token = generateToken(user)
const res = await request(app)
.get('/api/v1/projects')
.set('Authorization', `Bearer ${token}`)
expect(res.status).toBe(403)
})
it('returns 401 when token belongs to a deleted user', async () => {
const user = await createTestUser()
const token = generateToken(user)
await deleteUser(user.id)
const res = await request(app)
.get('/api/v1/projects')
.set('Authorization', `Bearer ${token}`)
expect(res.status).toBe(401)
})
it('returns 200 with valid token and correct role', async () => {
const user = await createTestUser({ role: 'member' })
const token = generateToken(user)
const res = await request(app)
.get('/api/v1/projects')
.set('Authorization', `Bearer ${token}`)
expect(res.status).toBe(200)
expect(res.body).toHaveProperty('data')
})
})
Input Validation Matrix
// tests/api/[resource]/validation.test.ts
describe('POST /api/v1/projects - Input Validation', () => {
const validPayload = {
name: 'My Project',
description: 'A test project',
visibility: 'private',
}
it('returns 422 when body is empty', async () => {
const res = await authedRequest('POST', '/api/v1/projects', {})
expect(res.status).toBe(422)
expect(res.body.error.details).toEqual(
expect.arrayContaining([
expect.objectContaining({ field: 'name', rule: 'required' }),
])
)
})
it.each([
['name', undefined, 'required'],
['name', '', 'min_length'],
['name', 'a'.repeat(256), 'max_length'],
['name', 123, 'type'],
['visibility', 'invalid', 'enum'],
['description', 'a'.repeat(10001), 'max_length'],
])('returns 422 when %s is %s (%s)', async (field, value, rule) => {
const payload = { ...validPayload, [field]: value }
if (value === undefined) delete payload[field]
const res = await authedRequest('POST', '/api/v1/projects', payload)
expect(res.status).toBe(422)
expect(res.body.error.details).toEqual(
expect.arrayContaining([
expect.objectContaining({ field, rule }),
])
)
})
it('rejects SQL injection in string fields', async () => {
const res = await authedRequest('POST', '/api/v1/projects', {
...validPayload,
name: "'; DROP TABLE projects; --",
})
// Should either reject (422) or sanitize and succeed (201)
expect([201, 422]).toContain(res.status)
if (res.status === 201) {
expect(res.body.data.name).not.toContain('DROP TABLE')
}
})
it('rejects XSS payloads in string fields', async () => {
const res = await authedRequest('POST', '/api/v1/projects', {
...validPayload,
name: '<script>alert("xss")</script>',
})
if (res.status === 201) {
expect(res.body.data.name).not.toContain('<script>')
}
})
it('accepts valid payload and returns 201 with created resource', async () => {
const res = await authedRequest('POST', '/api/v1/projects', validPayload)
expect(res.status).toBe(201)
expect(res.body.data).toMatchObject({
id: expect.any(String),
name: validPayload.name,
visibility: validPayload.visibility,