Skip to content

Performance Testing Support & Security Testing

Performance Testing in the Framework

What to Test

Type Measures Tool
Load test Behaviour under expected traffic Locust
Stress test Breaking point Locust ramp-up
Soak test Memory leaks over time Locust extended run
API latency p50/p95/p99 response times pytest + timer

Baseline Latency Assertions

Embed performance assertions in regular API tests:

import time
import pytest


@pytest.mark.performance
def test_user_list_responds_within_slo(api_client):
    start = time.monotonic()
    response = api_client.get("/users?limit=100")
    elapsed_ms = (time.monotonic() - start) * 1000

    assert response.status_code == 200
    assert elapsed_ms < 200, f"User list took {elapsed_ms:.1f}ms, SLO is 200ms"

Locust Load Test

# tests/performance/locustfile.py
from locust import HttpUser, task, between
import logging

log = logging.getLogger(__name__)


class ApiUser(HttpUser):
    wait_time = between(0.5, 2)

    def on_start(self) -> None:
        response = self.client.post("/auth/token", json={
            "email": "loadtest@example.com",
            "password": "Test@1234",
        })
        self.token = response.json()["access_token"]
        log.info("Load test user authenticated")

    @task(3)
    def get_users(self) -> None:
        self.client.get(
            "/users",
            headers={"Authorization": f"Bearer {self.token}"},
            name="/users",
        )

    @task(1)
    def create_order(self) -> None:
        self.client.post(
            "/orders",
            json={"items": ["SKU-001"]},
            headers={"Authorization": f"Bearer {self.token}"},
            name="/orders",
        )

Run:

locust -f tests/performance/locustfile.py \
  --headless -u 50 -r 5 --run-time 60s \
  --host https://api.staging.example.com

Distributed Execution

Split tests across multiple CI workers for speed:

# GitHub Actions matrix
strategy:
  matrix:
    shard: [1, 2, 3, 4]

- run: |
    uv run pytest -n auto \
      --shard-id=${{ matrix.shard }} \
      --num-shards=4

Security Testing Support

Security tests are automated checks for known vulnerability patterns. Not a replacement for penetration testing — a complement to it.

Authentication Tests

import pytest


@pytest.mark.security
class TestAuthSecurity:
    def test_endpoint_requires_token(self, api_client):
        response = api_client.get("/users", headers={})
        assert response.status_code == 401

    def test_expired_token_rejected(self, api_client, expired_token):
        response = api_client.get(
            "/users",
            headers={"Authorization": f"Bearer {expired_token}"},
        )
        assert response.status_code == 401

    def test_wrong_role_returns_403(self, api_client, user_token):
        response = api_client.delete(
            "/admin/users/some-id",
            headers={"Authorization": f"Bearer {user_token}"},
        )
        assert response.status_code == 403

    def test_tampered_token_rejected(self, api_client):
        tampered = "eyJhbGciOiJIUzI1NiJ9.TAMPERED.signature"
        response = api_client.get(
            "/users",
            headers={"Authorization": f"Bearer {tampered}"},
        )
        assert response.status_code == 401

Input Validation Tests

INJECTION_PAYLOADS = [
    ("sql", "' OR '1'='1"),
    ("sql", "'; DROP TABLE users; --"),
    ("xss", "<script>alert('xss')</script>"),
    ("xss", '"><img src=x onerror=alert(1)>'),
    ("path", "../../../etc/passwd"),
    ("null_byte", "user\x00admin"),
]


@pytest.mark.security
@pytest.mark.parametrize("attack_type,payload", INJECTION_PAYLOADS)
def test_injection_in_username_rejected(api_client, attack_type, payload):
    response = api_client.post("/users", json={"email": f"{payload}@test.com"})
    assert response.status_code in (400, 422), (
        f"{attack_type} injection was not rejected: {response.status_code}"
    )

Rate Limiting Tests

@pytest.mark.security
def test_login_rate_limited_after_failures(api_client):
    for _ in range(10):
        api_client.post("/auth/login", json={
            "email": "brute@example.com",
            "password": "WrongPassword1!",
        })

    response = api_client.post("/auth/login", json={
        "email": "brute@example.com",
        "password": "WrongPassword1!",
    })
    assert response.status_code == 429
    assert "Retry-After" in response.headers

Security Test Checklist

Category Tests
Auth Missing token → 401, expired → 401, wrong role → 403
Input SQL injection, XSS, path traversal, null bytes
Rate limiting Brute force triggers 429 with Retry-After
Data exposure Response never contains passwords or secrets
CORS Only allowed origins accepted
HTTPS HTTP redirects to HTTPS