api-test-generator

API Test Generator Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "api-test-generator" with this command: npx skills add matteocervelli/llms/matteocervelli-llms-api-test-generator

API Test Generator Skill

Purpose

This skill generates comprehensive integration tests for API endpoints, covering all HTTP methods, status codes, authentication, authorization, request/response validation, and error handling.

When to Use

  • Generate tests for REST API endpoints

  • Test GraphQL API queries and mutations

  • Validate API request/response contracts

  • Test API authentication and authorization

  • Verify API error handling and status codes

API Test Coverage

For each endpoint, test:

  • ✅ Success cases (200, 201, 204)

  • ✅ Validation errors (400, 422)

  • ✅ Authentication errors (401)

  • ✅ Authorization errors (403)

  • ✅ Not found errors (404)

  • ✅ Conflict errors (409)

  • ✅ Server errors (500)

  • ✅ Request body validation

  • ✅ Query parameter validation

  • ✅ Response schema validation

API Test Generation Workflow

  1. Analyze API Routes

Identify endpoints:

Read route definitions

cat src/routes/users.py cat src/controllers/user_controller.py

Identify:

- Endpoints and HTTP methods

- Path parameters

- Query parameters

- Request body schemas

- Response schemas

- Authentication requirements

- Authorization requirements

Map endpoints:

GET /api/users - List users (public) GET /api/users/:id - Get user (public) POST /api/users - Create user (admin only) PUT /api/users/:id - Update user (auth required, owner or admin) DELETE /api/users/:id - Delete user (auth required, owner or admin)

Deliverable: API endpoint inventory

  1. Generate REST API Test Suite

Test file structure:

""" Integration tests for Users API endpoints.

Endpoints tested:

  • GET /api/users
  • GET /api/users/:id
  • POST /api/users
  • PUT /api/users/:id
  • PATCH /api/users/:id
  • DELETE /api/users/:id """

import pytest from fastapi.testclient import TestClient from sqlalchemy.orm import Session

from src.models import User

============================================================================

GET /api/users - List Users

============================================================================

class TestGetUsers: """Tests for GET /api/users endpoint."""

def test_get_users_empty_returns_empty_list(self, client: TestClient):
    """Test GET /api/users with no users returns empty list."""
    # Act
    response = client.get("/api/users")

    # Assert
    assert response.status_code == 200
    assert response.json() == []

def test_get_users_returns_user_list(
    self, client: TestClient, db: Session
):
    """Test GET /api/users returns list of users."""
    # Arrange: Create test users
    users = [
        User(name=f"User {i}", email=f"user{i}@example.com")
        for i in range(3)
    ]
    db.add_all(users)
    db.commit()

    # Act
    response = client.get("/api/users")

    # Assert
    assert response.status_code == 200
    data = response.json()
    assert len(data) == 3
    assert all("id" in user for user in data)
    assert all("name" in user for user in data)
    assert all("email" in user for user in data)

def test_get_users_with_pagination(
    self, client: TestClient, db: Session
):
    """Test GET /api/users with pagination parameters."""
    # Arrange: Create 10 users
    users = [
        User(name=f"User {i}", email=f"user{i}@example.com")
        for i in range(10)
    ]
    db.add_all(users)
    db.commit()

    # Act
    response = client.get("/api/users?limit=5&offset=0")

    # Assert
    assert response.status_code == 200
    data = response.json()
    assert len(data) == 5

def test_get_users_with_search_filter(
    self, client: TestClient, db: Session
):
    """Test GET /api/users with search query."""
    # Arrange
    users = [
        User(name="Alice", email="alice@example.com"),
        User(name="Bob", email="bob@example.com"),
        User(name="Alice Smith", email="asmith@example.com"),
    ]
    db.add_all(users)
    db.commit()

    # Act
    response = client.get("/api/users?search=alice")

    # Assert
    assert response.status_code == 200
    data = response.json()
    assert len(data) == 2
    assert all("alice" in user["name"].lower() for user in data)

def test_get_users_invalid_limit_returns_400(self, client: TestClient):
    """Test GET /api/users with invalid limit parameter."""
    # Act
    response = client.get("/api/users?limit=-1")

    # Assert
    assert response.status_code == 400
    assert "limit" in response.json()["detail"].lower()

============================================================================

GET /api/users/:id - Get User by ID

============================================================================

class TestGetUserById: """Tests for GET /api/users/:id endpoint."""

def test_get_user_by_id_returns_user(
    self, client: TestClient, test_user: User
):
    """Test GET /api/users/:id returns specific user."""
    # Act
    response = client.get(f"/api/users/{test_user.id}")

    # Assert
    assert response.status_code == 200
    data = response.json()
    assert data["id"] == test_user.id
    assert data["name"] == test_user.name
    assert data["email"] == test_user.email
    assert "password" not in data  # Sensitive data not included

def test_get_user_nonexistent_id_returns_404(self, client: TestClient):
    """Test GET /api/users/:id with nonexistent ID returns 404."""
    # Act
    response = client.get("/api/users/99999")

    # Assert
    assert response.status_code == 404
    assert "not found" in response.json()["detail"].lower()

def test_get_user_invalid_id_format_returns_400(self, client: TestClient):
    """Test GET /api/users/:id with invalid ID format returns 400."""
    # Act
    response = client.get("/api/users/invalid-id")

    # Assert
    assert response.status_code == 400

============================================================================

POST /api/users - Create User

============================================================================

class TestCreateUser: """Tests for POST /api/users endpoint."""

def test_create_user_valid_data_returns_created(
    self, client: TestClient, db: Session, admin_headers: dict
):
    """Test POST /api/users with valid data creates user."""
    # Arrange
    user_data = {
        "name": "New User",
        "email": "newuser@example.com",
        "password": "SecurePass123"
    }

    # Act
    response = client.post(
        "/api/users",
        json=user_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 201
    data = response.json()
    assert data["name"] == user_data["name"]
    assert data["email"] == user_data["email"]
    assert "id" in data
    assert "password" not in data  # Password not returned
    assert "created_at" in data

    # Verify in database
    user = db.query(User).filter_by(email=user_data["email"]).first()
    assert user is not None
    assert user.name == user_data["name"]

def test_create_user_missing_required_field_returns_400(
    self, client: TestClient, admin_headers: dict
):
    """Test POST /api/users with missing required field returns 400."""
    # Arrange: Missing email
    invalid_data = {
        "name": "User",
        "password": "password"
    }

    # Act
    response = client.post(
        "/api/users",
        json=invalid_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 400
    assert "email" in response.json()["detail"].lower()

def test_create_user_invalid_email_returns_400(
    self, client: TestClient, admin_headers: dict
):
    """Test POST /api/users with invalid email format returns 400."""
    # Arrange
    invalid_data = {
        "name": "User",
        "email": "not-an-email",
        "password": "password"
    }

    # Act
    response = client.post(
        "/api/users",
        json=invalid_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 400
    assert "email" in response.json()["detail"].lower()

def test_create_user_weak_password_returns_400(
    self, client: TestClient, admin_headers: dict
):
    """Test POST /api/users with weak password returns 400."""
    # Arrange
    invalid_data = {
        "name": "User",
        "email": "user@example.com",
        "password": "123"  # Too short
    }

    # Act
    response = client.post(
        "/api/users",
        json=invalid_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 400
    assert "password" in response.json()["detail"].lower()

def test_create_user_duplicate_email_returns_409(
    self, client: TestClient, test_user: User, admin_headers: dict
):
    """Test POST /api/users with duplicate email returns 409."""
    # Arrange
    duplicate_data = {
        "name": "Another User",
        "email": test_user.email,  # Duplicate
        "password": "password"
    }

    # Act
    response = client.post(
        "/api/users",
        json=duplicate_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 409
    assert "already exists" in response.json()["detail"].lower()

def test_create_user_without_auth_returns_401(
    self, client: TestClient
):
    """Test POST /api/users without authentication returns 401."""
    # Arrange
    user_data = {
        "name": "User",
        "email": "user@example.com",
        "password": "password"
    }

    # Act
    response = client.post("/api/users", json=user_data)

    # Assert
    assert response.status_code == 401

def test_create_user_as_non_admin_returns_403(
    self, client: TestClient, auth_headers: dict
):
    """Test POST /api/users as non-admin user returns 403."""
    # Arrange
    user_data = {
        "name": "User",
        "email": "user@example.com",
        "password": "password"
    }

    # Act
    response = client.post(
        "/api/users",
        json=user_data,
        headers=auth_headers  # Regular user, not admin
    )

    # Assert
    assert response.status_code == 403

============================================================================

PUT /api/users/:id - Update User (Full Replace)

============================================================================

class TestUpdateUser: """Tests for PUT /api/users/:id endpoint."""

def test_update_user_own_account_returns_updated(
    self, client: TestClient, test_user: User, auth_headers: dict, db: Session
):
    """Test PUT /api/users/:id to update own account."""
    # Arrange
    update_data = {
        "name": "Updated Name",
        "email": test_user.email  # Same email
    }

    # Act
    response = client.put(
        f"/api/users/{test_user.id}",
        json=update_data,
        headers=auth_headers
    )

    # Assert
    assert response.status_code == 200
    data = response.json()
    assert data["name"] == "Updated Name"

    # Verify in database
    db.refresh(test_user)
    assert test_user.name == "Updated Name"

def test_update_user_other_account_as_admin_succeeds(
    self, client: TestClient, test_user: User, admin_headers: dict, db: Session
):
    """Test PUT /api/users/:id as admin to update other user."""
    # Arrange
    update_data = {
        "name": "Admin Updated",
        "email": test_user.email
    }

    # Act
    response = client.put(
        f"/api/users/{test_user.id}",
        json=update_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 200

def test_update_user_other_account_as_regular_user_returns_403(
    self, client: TestClient, db: Session, auth_headers: dict
):
    """Test PUT /api/users/:id to update other user returns 403."""
    # Arrange: Create another user
    other_user = User(name="Other", email="other@example.com")
    db.add(other_user)
    db.commit()

    update_data = {"name": "Unauthorized Update"}

    # Act
    response = client.put(
        f"/api/users/{other_user.id}",
        json=update_data,
        headers=auth_headers  # Regular user, not admin
    )

    # Assert
    assert response.status_code == 403

def test_update_user_nonexistent_returns_404(
    self, client: TestClient, admin_headers: dict
):
    """Test PUT /api/users/:id with nonexistent ID returns 404."""
    # Arrange
    update_data = {"name": "Updated"}

    # Act
    response = client.put(
        "/api/users/99999",
        json=update_data,
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 404

============================================================================

PATCH /api/users/:id - Partial Update User

============================================================================

class TestPatchUser: """Tests for PATCH /api/users/:id endpoint."""

def test_patch_user_single_field_updates(
    self, client: TestClient, test_user: User, auth_headers: dict, db: Session
):
    """Test PATCH /api/users/:id updates only specified field."""
    # Arrange
    original_email = test_user.email
    patch_data = {"name": "Patched Name"}

    # Act
    response = client.patch(
        f"/api/users/{test_user.id}",
        json=patch_data,
        headers=auth_headers
    )

    # Assert
    assert response.status_code == 200
    data = response.json()
    assert data["name"] == "Patched Name"
    assert data["email"] == original_email  # Unchanged

    # Verify in database
    db.refresh(test_user)
    assert test_user.name == "Patched Name"
    assert test_user.email == original_email

============================================================================

DELETE /api/users/:id - Delete User

============================================================================

class TestDeleteUser: """Tests for DELETE /api/users/:id endpoint."""

def test_delete_user_own_account_returns_no_content(
    self, client: TestClient, test_user: User, auth_headers: dict, db: Session
):
    """Test DELETE /api/users/:id to delete own account."""
    # Act
    response = client.delete(
        f"/api/users/{test_user.id}",
        headers=auth_headers
    )

    # Assert
    assert response.status_code == 204

    # Verify in database
    deleted_user = db.query(User).filter_by(id=test_user.id).first()
    assert deleted_user is None

def test_delete_user_as_admin_succeeds(
    self, client: TestClient, test_user: User, admin_headers: dict, db: Session
):
    """Test DELETE /api/users/:id as admin."""
    # Act
    response = client.delete(
        f"/api/users/{test_user.id}",
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 204

def test_delete_user_other_account_returns_403(
    self, client: TestClient, db: Session, auth_headers: dict
):
    """Test DELETE /api/users/:id to delete other user returns 403."""
    # Arrange
    other_user = User(name="Other", email="other@example.com")
    db.add(other_user)
    db.commit()

    # Act
    response = client.delete(
        f"/api/users/{other_user.id}",
        headers=auth_headers
    )

    # Assert
    assert response.status_code == 403

def test_delete_user_without_auth_returns_401(
    self, client: TestClient, test_user: User
):
    """Test DELETE /api/users/:id without auth returns 401."""
    # Act
    response = client.delete(f"/api/users/{test_user.id}")

    # Assert
    assert response.status_code == 401

def test_delete_user_nonexistent_returns_404(
    self, client: TestClient, admin_headers: dict
):
    """Test DELETE /api/users/:id with nonexistent ID returns 404."""
    # Act
    response = client.delete(
        "/api/users/99999",
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 404

Deliverable: Comprehensive REST API tests

  1. Generate GraphQL API Tests

GraphQL test structure:

""" Integration tests for GraphQL API. """

import pytest from fastapi.testclient import TestClient from sqlalchemy.orm import Session

class TestGraphQLQueries: """Tests for GraphQL queries."""

def test_query_users_returns_list(
    self, client: TestClient, db: Session
):
    """Test users query returns list."""
    # Arrange
    create_test_users(db, count=3)

    query = """
    query {
        users {
            id
            name
            email
        }
    }
    """

    # Act
    response = client.post("/graphql", json={"query": query})

    # Assert
    assert response.status_code == 200
    data = response.json()["data"]
    assert len(data["users"]) == 3

def test_query_user_by_id_returns_user(
    self, client: TestClient, test_user: User
):
    """Test user query by ID returns specific user."""
    # Arrange
    query = f"""
    query {{
        user(id: {test_user.id}) {{
            id
            name
            email
        }}
    }}
    """

    # Act
    response = client.post("/graphql", json={"query": query})

    # Assert
    assert response.status_code == 200
    data = response.json()["data"]["user"]
    assert data["id"] == test_user.id
    assert data["name"] == test_user.name

class TestGraphQLMutations: """Tests for GraphQL mutations."""

def test_create_user_mutation_creates_user(
    self, client: TestClient, db: Session, admin_headers: dict
):
    """Test createUser mutation creates new user."""
    # Arrange
    mutation = """
    mutation {
        createUser(input: {
            name: "New User",
            email: "newuser@example.com",
            password: "SecurePass123"
        }) {
            user {
                id
                name
                email
            }
        }
    }
    """

    # Act
    response = client.post(
        "/graphql",
        json={"query": mutation},
        headers=admin_headers
    )

    # Assert
    assert response.status_code == 200
    data = response.json()["data"]["createUser"]["user"]
    assert data["name"] == "New User"
    assert data["email"] == "newuser@example.com"

    # Verify in database
    user = db.query(User).filter_by(email="newuser@example.com").first()
    assert user is not None

Deliverable: GraphQL API tests

HTTP Status Code Testing

Test all relevant status codes:

200 OK - Successful GET/PUT/PATCH

def test_returns_200_on_success(client): response = client.get("/api/resource") assert response.status_code == 200

201 Created - Successful POST

def test_returns_201_on_create(client): response = client.post("/api/resource", json=data) assert response.status_code == 201

204 No Content - Successful DELETE

def test_returns_204_on_delete(client): response = client.delete("/api/resource/1") assert response.status_code == 204

400 Bad Request - Validation error

def test_returns_400_on_invalid_input(client): response = client.post("/api/resource", json=invalid_data) assert response.status_code == 400

401 Unauthorized - Missing/invalid auth

def test_returns_401_without_auth(client): response = client.get("/api/protected") assert response.status_code == 401

403 Forbidden - Insufficient permissions

def test_returns_403_without_permission(client, user_token): response = client.delete("/api/admin/resource", headers=user_token) assert response.status_code == 403

404 Not Found - Resource doesn't exist

def test_returns_404_for_nonexistent(client): response = client.get("/api/resource/99999") assert response.status_code == 404

409 Conflict - Duplicate resource

def test_returns_409_on_duplicate(client): response = client.post("/api/resource", json=existing_data) assert response.status_code == 409

422 Unprocessable Entity - Semantic error

def test_returns_422_on_semantic_error(client): response = client.post("/api/resource", json=invalid_semantic_data) assert response.status_code == 422

500 Internal Server Error - Server error

def test_returns_500_on_server_error(client, mock_error): response = client.get("/api/resource") assert response.status_code == 500

Best Practices

  • Test all HTTP methods: GET, POST, PUT, PATCH, DELETE

  • Test all status codes: Success and error responses

  • Validate request bodies: Required fields, formats, constraints

  • Validate response bodies: Schema, fields, data types

  • Test authentication: With/without tokens, expired tokens

  • Test authorization: Different user roles and permissions

  • Test edge cases: Empty lists, null values, max limits

  • Verify database state: Check data persisted correctly

  • Use descriptive test names: Clearly state what's being tested

  • Group by endpoint: Organize tests by API endpoint

Quality Checklist

Before completing API tests:

  • All endpoints tested

  • All HTTP methods tested

  • Success cases (200, 201, 204) covered

  • Error cases (400, 401, 403, 404, 409) covered

  • Request validation tested

  • Response schema validated

  • Authentication tested

  • Authorization tested

  • Edge cases covered

  • Database state verified

  • All tests pass

  • Tests are independent

Integration with Testing Workflow

Input: API routes and endpoints Process: Analyze → Generate tests → Run & verify Output: Comprehensive API test suite Next Step: API documentation or deployment

Remember

  • Test all endpoints and HTTP methods

  • Test success and error cases

  • Validate request and response schemas

  • Test authentication and authorization

  • Verify database state after operations

  • Use appropriate status codes

  • Keep tests focused on one scenario

  • Tests serve as API documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

documentation-updater

No summary provided by upstream source.

Repository SourceNeeds Review
General

coverage-analyzer

No summary provided by upstream source.

Repository SourceNeeds Review
General

doc-analyzer

No summary provided by upstream source.

Repository SourceNeeds Review