Claude-skill-registry api-testing

Test FastAPI endpoints with pytest and generate API documentation. Use when creating new APIs or verifying existing endpoints work correctly.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-testing" ~/.claude/skills/majiayu000-claude-skill-registry-api-testing && rm -rf "$T"
manifest: skills/data/api-testing/SKILL.md
safety · automated scan (medium risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
  • pip install
  • makes HTTP requests (curl)
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content

You help test FastAPI endpoints for the QA Team Portal backend using pytest and manual testing tools.

When to Use This Skill

  • Testing new API endpoints after creation
  • Verifying authentication/authorization works
  • Testing CRUD operations
  • Checking error handling and validation
  • Load/stress testing APIs
  • Generating API documentation examples

Testing Approaches

1. Automated Testing with Pytest

Unit Tests (Fast, Isolated)

# tests/unit/test_team_service.py
import pytest
from app.services.team_service import TeamService

def test_validate_team_member_data():
    service = TeamService()
    data = {"name": "John Doe", "role": "QA Lead"}
    assert service.validate(data) is True

def test_validate_rejects_invalid_email():
    service = TeamService()
    data = {"name": "John", "email": "invalid"}
    with pytest.raises(ValueError):
        service.validate(data)

Integration Tests (Full API Flow)

# tests/integration/test_api_team_members.py
import pytest
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_get_team_members():
    response = client.get("/api/v1/team-members")
    assert response.status_code == 200
    assert isinstance(response.json(), list)

def test_create_team_member_requires_auth():
    data = {"name": "John Doe", "role": "QA Lead"}
    response = client.post("/api/v1/team-members", json=data)
    assert response.status_code == 401

def test_create_team_member_with_auth(admin_token):
    headers = {"Authorization": f"Bearer {admin_token}"}
    data = {
        "name": "John Doe",
        "role": "QA Lead",
        "email": "john@example.com"
    }
    response = client.post("/api/v1/team-members", json=data, headers=headers)
    assert response.status_code == 201
    assert response.json()["name"] == "John Doe"

def test_update_team_member(admin_token, test_team_member):
    headers = {"Authorization": f"Bearer {admin_token}"}
    data = {"name": "Jane Doe"}
    response = client.put(
        f"/api/v1/team-members/{test_team_member.id}",
        json=data,
        headers=headers
    )
    assert response.status_code == 200
    assert response.json()["name"] == "Jane Doe"

def test_delete_team_member(admin_token, test_team_member):
    headers = {"Authorization": f"Bearer {admin_token}"}
    response = client.delete(
        f"/api/v1/team-members/{test_team_member.id}",
        headers=headers
    )
    assert response.status_code == 204

Pytest Fixtures

# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.db.base import Base
from app.api.deps import get_db

# Test database
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
TestingSessionLocal = sessionmaker(bind=engine)

@pytest.fixture(scope="function")
def db():
    Base.metadata.create_all(bind=engine)
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()
        Base.metadata.drop_all(bind=engine)

@pytest.fixture
def client(db):
    def override_get_db():
        try:
            yield db
        finally:
            db.close()
    app.dependency_overrides[get_db] = override_get_db
    return TestClient(app)

@pytest.fixture
def admin_token(client):
    response = client.post("/api/v1/auth/login", json={
        "email": "admin@test.com",
        "password": "testpass123"
    })
    return response.json()["access_token"]

@pytest.fixture
def test_team_member(db):
    from app.models.team_member import TeamMember
    member = TeamMember(
        name="Test User",
        role="QA Engineer",
        email="test@test.com"
    )
    db.add(member)
    db.commit()
    db.refresh(member)
    return member

2. Manual Testing with curl

# Health check
curl http://localhost:8000/health

# Get all team members (public)
curl http://localhost:8000/api/v1/team-members

# Login
TOKEN=$(curl -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@test.com","password":"pass"}' \
  | jq -r '.access_token')

# Create team member (admin)
curl -X POST http://localhost:8000/api/v1/team-members \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "role": "QA Lead",
    "email": "john@example.com"
  }'

# Upload profile photo
curl -X POST http://localhost:8000/api/v1/team-members/123/photo \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@profile.jpg"

# Get with filters
curl "http://localhost:8000/api/v1/team-members?role=QA%20Lead&active=true"

3. Testing with HTTPie (Prettier Output)

# Install httpie
pip install httpie

# Login
http POST localhost:8000/api/v1/auth/login email=admin@test.com password=pass

# Create with auth
http POST localhost:8000/api/v1/team-members \
  Authorization:"Bearer $TOKEN" \
  name="John Doe" \
  role="QA Lead" \
  email="john@example.com"

# Pretty print JSON
http GET localhost:8000/api/v1/team-members | jq '.'

Running Tests

cd backend

# Run all tests
uv run pytest

# Run with verbose output
uv run pytest -v

# Run specific test file
uv run pytest tests/integration/test_api_team_members.py

# Run specific test
uv run pytest tests/integration/test_api_team_members.py::test_create_team_member

# Run with coverage
uv run pytest --cov=app --cov-report=html

# Run only integration tests
uv run pytest tests/integration/

# Show print statements
uv run pytest -s

# Stop on first failure
uv run pytest -x

# Run tests matching pattern
uv run pytest -k "team_member"

Test Coverage

# Generate coverage report
uv run pytest --cov=app --cov-report=term-missing

# Generate HTML report
uv run pytest --cov=app --cov-report=html
open htmlcov/index.html

# Coverage for specific module
uv run pytest --cov=app.api.v1.endpoints --cov-report=term

Load Testing

# Install locust
uv pip install locust

# Create locustfile.py
cat > locustfile.py <<'EOF'
from locust import HttpUser, task, between

class APIUser(HttpUser):
    wait_time = between(1, 3)

    @task
    def get_team_members(self):
        self.client.get("/api/v1/team-members")

    @task(3)
    def get_updates(self):
        self.client.get("/api/v1/updates")
EOF

# Run load test
uv run locust -f locustfile.py --host=http://localhost:8000

# Or headless mode
uv run locust -f locustfile.py --host=http://localhost:8000 \
  --users 100 --spawn-rate 10 --run-time 1m --headless

API Documentation Testing

# Access interactive docs
open http://localhost:8000/api/v1/docs

# Get OpenAPI schema
curl http://localhost:8000/api/v1/openapi.json | jq '.' > openapi.json

# Validate OpenAPI schema
npx @stoplight/spectral-cli lint openapi.json

Test Checklist

For each endpoint, verify:

  • Success cases - Returns 200/201/204 as expected
  • Authentication - Returns 401 without token
  • Authorization - Returns 403 for insufficient permissions
  • Validation - Returns 422 for invalid data
  • Not Found - Returns 404 for non-existent resources
  • Edge cases - Empty lists, null values, boundary conditions
  • Error handling - Doesn't expose sensitive info in errors
  • Rate limiting - Enforced on sensitive endpoints
  • CORS - Allows configured origins only
  • Response format - Matches schema definition

Common Test Patterns

Testing Authentication

def test_endpoint_requires_authentication(client):
    response = client.post("/api/v1/admin/users")
    assert response.status_code == 401

def test_endpoint_rejects_expired_token(client, expired_token):
    headers = {"Authorization": f"Bearer {expired_token}"}
    response = client.get("/api/v1/admin/users", headers=headers)
    assert response.status_code == 401

Testing Validation

def test_rejects_invalid_email(client, admin_token):
    headers = {"Authorization": f"Bearer {admin_token}"}
    data = {"name": "John", "email": "invalid"}
    response = client.post("/api/v1/team-members", json=data, headers=headers)
    assert response.status_code == 422
    assert "email" in response.json()["detail"][0]["loc"]

Testing Pagination

def test_pagination_limits_results(client):
    response = client.get("/api/v1/team-members?limit=5")
    assert len(response.json()) <= 5

def test_pagination_skip_offset(client):
    response1 = client.get("/api/v1/team-members?skip=0&limit=2")
    response2 = client.get("/api/v1/team-members?skip=2&limit=2")
    assert response1.json()[0]["id"] != response2.json()[0]["id"]

Output Format

After testing, report:

  1. Tests Run: X passed, Y failed
  2. Coverage: X% of code covered
  3. Failed Tests: List with error messages
  4. Performance: Average response time for key endpoints
  5. Issues Found: Any bugs or unexpected behavior
  6. Recommendations: Suggested improvements

Best Practices

  1. Test pyramid: More unit tests, fewer integration tests
  2. Independent tests: Each test should be isolated
  3. Descriptive names:
    test_create_team_member_requires_admin_role
  4. Use fixtures: Share test data setup
  5. Test error cases: Not just happy path
  6. Mock external services: Don't depend on external APIs
  7. Fast tests: Keep test suite under 1 minute if possible