Claude
Skills
Sign in
Back

api-test-suite-builder

Included with Lifetime
$97 forever

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.

Backend & APIsscripts

What this skill does

# 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: '<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,
      

Related in Backend & APIs