- backend/load_tests/__init__.py — empty package marker; stops pytest discovery - backend/load_tests/locustfile.py — DocuVaultUser skeleton (on_start, _auth_headers, list_documents, upload_document, refresh_token methods) and check_sla SLA listener; all bodies raise NotImplementedError - No application imports; TEST_EMAIL/TEST_PASSWORD from env vars only - Full implementation in plan 06-03
73 lines
2.5 KiB
Python
73 lines
2.5 KiB
Python
"""Locust load test for DocuVault — D-04, D-05, D-06.
|
|
|
|
Run:
|
|
locust --headless --users 50 --spawn-rate 10 --run-time 5m \\
|
|
--host http://localhost:8000 \\
|
|
--csv backend/load_tests/results \\
|
|
-f backend/load_tests/locustfile.py
|
|
|
|
Prerequisites: a user with LOAD_TEST_EMAIL/LOAD_TEST_PASSWORD must exist in the
|
|
DB, or on_start will register one automatically (idempotent — 409 is ignored).
|
|
|
|
Full implementation is in plan 06-03. This skeleton defines the class shape and
|
|
SLA listener so downstream CI tooling can import the file without errors.
|
|
"""
|
|
import os
|
|
|
|
from locust import HttpUser, between, events, task
|
|
|
|
TEST_EMAIL = os.environ.get("LOAD_TEST_EMAIL", "loadtest@example.com")
|
|
TEST_PASSWORD = os.environ.get("LOAD_TEST_PASSWORD", "Loadtest123!@#")
|
|
|
|
|
|
class DocuVaultUser(HttpUser):
|
|
"""Simulated DocuVault end-user.
|
|
|
|
Scenarios (weighted tasks):
|
|
- list_documents (weight 5) — GET /api/documents/
|
|
- upload_document (weight 2) — POST /api/documents/upload-url flow
|
|
- refresh_token (weight 1) — POST /api/auth/refresh
|
|
"""
|
|
|
|
wait_time = between(0.5, 2.0)
|
|
access_token: str = ""
|
|
|
|
def on_start(self) -> None:
|
|
"""Authenticate before running tasks.
|
|
|
|
Registers the load-test user if it does not exist (409 is silently
|
|
ignored), then performs a login to obtain an access token.
|
|
"""
|
|
raise NotImplementedError("implementation in 06-03")
|
|
|
|
def _auth_headers(self) -> dict:
|
|
"""Return Authorization header dict for authenticated requests."""
|
|
raise NotImplementedError("implementation in 06-03")
|
|
|
|
@task(5)
|
|
def list_documents(self) -> None:
|
|
"""GET /api/documents/ — highest-frequency task."""
|
|
raise NotImplementedError("implementation in 06-03")
|
|
|
|
@task(2)
|
|
def upload_document(self) -> None:
|
|
"""Two-step presigned upload: POST /upload-url → PUT MinIO → POST /{id}/confirm."""
|
|
raise NotImplementedError("implementation in 06-03")
|
|
|
|
@task(1)
|
|
def refresh_token(self) -> None:
|
|
"""POST /api/auth/refresh — rotates the httpOnly refresh cookie."""
|
|
raise NotImplementedError("implementation in 06-03")
|
|
|
|
|
|
@events.quitting.add_listener
|
|
def check_sla(environment, **kwargs) -> None:
|
|
"""Fail the Locust run if SLA thresholds are breached.
|
|
|
|
Thresholds (from D-06):
|
|
- p95 response time > 200 ms → exit code 1
|
|
- p99 response time > 500 ms → exit code 1
|
|
- error ratio > 1% → exit code 1
|
|
"""
|
|
raise NotImplementedError("implementation in 06-03")
|