--- name: api-test-suite-builder description: > Generate comprehensive API test suites from route definitions across frameworks. Covers auth testing, input validation, contract testing, load testing with k6, API mocking, and OpenAPI-driven test generation. Use when adding new APIs, auditing test coverage, or building regression suites. license: MIT + Commons Clause metadata: version: 1.0.0 author: borghei category: engineering domain: api-testing tier: POWERFUL updated: 2026-03-09 frameworks: vitest, pytest, supertest, httpx, k6, pact --- # 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 ```bash # 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 ```bash 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 ```bash grep -rn "@\(app\|router\)\.\(get\|post\|put\|delete\|patch\)" . --include="*.py" | \ grep -oE "(get|post|put|delete|patch)\(['\"][^'\"]*['\"]" ``` ### Go (net/http, Chi, Gin) ```bash 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: ```typescript // 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 ```typescript // 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: '', }) if (res.status === 201) { expect(res.body.data.name).not.toContain('