""" FASHIONISTAR — Enterprise Auth 100k RPS Locust Stress Test ============================================================ Target: 100,000 RPS across authentication endpoints. Endpoints under test: POST /api/v1/auth/register/ — User registration POST /api/v1/auth/login/ — JWT login GET /api/v1/auth/me/ — Authenticated profile fetch POST /api/v1/auth/token/refresh/ — JWT refresh rotation POST /api/v1/auth/logout/ — Token blacklist Load Profile: Ramp from 100 → 1,000 → 10,000 users over 3 minutes. For true 100k RPS, run distributed across multiple machines: locust --master + N x locust --worker Usage: # Install: uv pip install locust # Local run (10k VUs for 2 min): locust -f stress_tests/04_auth_100k_rps.py \\ --headless -u 10000 -r 500 \\ --run-time 2m \\ --host http://127.0.0.1:8000 \\ --csv=results/auth_stress # Distributed (master node): locust -f stress_tests/04_auth_100k_rps.py --master \\ --host http://127.0.0.1:8000 \\ --expect-workers 4 # Worker nodes (run one per CPU core): locust -f stress_tests/04_auth_100k_rps.py --worker --master-host # Quick validation (500 users, 30 seconds): locust -f stress_tests/04_auth_100k_rps.py \\ --headless -u 500 -r 100 --run-time 30s \\ --host http://127.0.0.1:8000 Pass criteria (enterprise production): - p95 response time < 500ms for all endpoints - p99 response time < 2000ms - Error rate (4xx + 5xx) < 1% - Zero 500 errors on login/me endpoints - Max RPS achieved > 5,000 on single local instance Results interpretation: - 429 (throttle) → expected and healthy — throttle is working - 400 on duplicate register → expected — idempotency working - 401 on me without token → bug in test setup - 500 on any endpoint → CRITICAL — must fix before prod """ import uuid import random import logging from typing import Optional from locust import HttpUser, task, between, events logger = logging.getLogger(__name__) # ───────────────────────────────────────────────────────────────────────────── # Shared token pool for authenticated requests (thread-safe via list) # ───────────────────────────────────────────────────────────────────────────── _TOKEN_POOL: list[dict] = [] # [{access, refresh, email}, ...] _POOL_MAX = 1000 # Maximum tokens to maintain # ───────────────────────────────────────────────────────────────────────────── # HELPER FUNCTIONS # ───────────────────────────────────────────────────────────────────────────── def random_email(prefix: str = "stress") -> str: return f"{prefix}.{uuid.uuid4().hex[:8]}@fashionistar-load.io" def extract_tokens(response) -> Optional[dict]: """Parse access and refresh tokens from the response body.""" try: data = response.json() payload = data.get('data', data) access = payload.get('access') or payload.get('access_token') refresh = payload.get('refresh') or payload.get('refresh_token') if access and refresh: return {'access': access, 'refresh': refresh} except Exception: pass return None # ───────────────────────────────────────────────────────────────────────────── # USER CLASSES # ───────────────────────────────────────────────────────────────────────────── class RegistrationUser(HttpUser): """ Simulates new users registering. Weight: 10% of load (registration is less frequent than login). """ weight = 1 wait_time = between(0.1, 0.5) @task(1) def register(self): """POST /api/v1/auth/register/ — unique user each time.""" email = random_email("reg") idem_key = str(uuid.uuid4()) with self.client.post( "/api/v1/auth/register/", json={ "email": email, "password": "StressTest123!", "password2": "StressTest123!", "first_name": "Stress", "last_name": "User", "role": "client", }, headers={"X-Idempotency-Key": idem_key}, catch_response=True, name="/api/v1/auth/register/", ) as resp: if resp.status_code == 201: resp.success() elif resp.status_code in (400, 429): # 400 = duplicate (OK in stress); 429 = throttled (OK + healthy) resp.success() elif resp.status_code == 409: # Idempotency lock conflict — pass (expected under concurrency) resp.success() else: resp.failure( f"Unexpected {resp.status_code} on /register/: {resp.text[:200]}" ) class LoginUser(HttpUser): """ Simulates returning users who log in repeatedly. Weight: 50% of load. Pre-creates user on start(). """ weight = 5 wait_time = between(0.05, 0.3) def on_start(self): """Pre-register a user so we can log in during the test.""" self.email = random_email("login") self.password = "StressLogin123!" resp = self.client.post( "/api/v1/auth/register/", json={ "email": self.email, "password": self.password, "password2": self.password, "first_name": "Login", "last_name": "Stress", "role": "client", }, name="/api/v1/auth/register/ [setup]", ) # We won't verify OTP in stress tests — activate user manually # by calling a test-specific endpoint or pre-seeding the DB. self.access_token = None self.refresh_token = None # Attempt login in case user is pre-verified in test DB self._login() def _login(self): """Login and store tokens.""" resp = self.client.post( "/api/v1/auth/login/", json={ "email_or_phone": self.email, "password": self.password, }, name="/api/v1/auth/login/", ) tokens = extract_tokens(resp) if tokens: self.access_token = tokens['access'] self.refresh_token = tokens['refresh'] if len(_TOKEN_POOL) < _POOL_MAX: _TOKEN_POOL.append({ 'access': self.access_token, 'refresh': self.refresh_token, 'email': self.email, }) @task(5) def login(self): """POST /api/v1/auth/login/ — high frequency.""" with self.client.post( "/api/v1/auth/login/", json={ "email_or_phone": self.email, "password": self.password, }, catch_response=True, name="/api/v1/auth/login/", ) as resp: if resp.status_code in (200, 429, 400): resp.success() elif resp.status_code == 500: resp.failure(f"CRITICAL 500 on /login/: {resp.text[:200]}") else: resp.failure(f"Unexpected {resp.status_code}: {resp.text[:100]}") @task(3) def refresh_token(self): """POST /api/v1/auth/token/refresh/ — frequent refresh cycle.""" if not self.refresh_token: return with self.client.post( "/api/v1/auth/token/refresh/", json={"refresh": self.refresh_token}, catch_response=True, name="/api/v1/auth/token/refresh/", ) as resp: if resp.status_code == 200: tokens = extract_tokens(resp) if tokens: self.access_token = tokens['access'] self.refresh_token = tokens['refresh'] resp.success() elif resp.status_code in (400, 401, 429): # Token expired/blacklisted → re-login self._login() resp.success() else: resp.failure(f"Unexpected refresh {resp.status_code}: {resp.text[:100]}") class AuthenticatedUser(HttpUser): """ Simulates authenticated users consuming protected endpoints. Weight: 40% of load. """ weight = 4 wait_time = between(0.02, 0.2) def on_start(self): """Grab a token from the shared pool or log in fresh.""" self.access_token = None if _TOKEN_POOL: token_data = random.choice(_TOKEN_POOL) self.access_token = token_data['access'] # If no pool yet, start unauthenticated — /me/ will return 401 (expected) @property def auth_headers(self): if self.access_token: return {"Authorization": f"Bearer {self.access_token}"} return {} @task(10) def get_me(self): """GET /api/v1/auth/me/ — highest frequency (profile rehydration).""" with self.client.get( "/api/v1/auth/me/", headers=self.auth_headers, catch_response=True, name="/api/v1/auth/me/", ) as resp: if resp.status_code in (200, 401, 429): resp.success() elif resp.status_code == 500: resp.failure(f"CRITICAL 500 on /me/: {resp.text[:200]}") else: resp.failure(f"Unexpected {resp.status_code} on /me/: {resp.text[:100]}") @task(2) def list_sessions(self): """GET /api/v1/auth/sessions/ — session list (security dashboard).""" with self.client.get( "/api/v1/auth/sessions/", headers=self.auth_headers, catch_response=True, name="/api/v1/auth/sessions/", ) as resp: if resp.status_code in (200, 401, 403, 429): resp.success() elif resp.status_code == 500: resp.failure(f"CRITICAL 500 on /sessions/: {resp.text[:200]}") else: resp.failure(f"Unexpected {resp.status_code} on /sessions/: {resp.text[:100]}") @task(1) def health_check(self): """GET /health/ — Kubernetes readiness probe (must always be < 50ms).""" with self.client.get( "/health/", catch_response=True, name="/health/", ) as resp: if resp.status_code == 200: resp.success() else: resp.failure(f"Health check failed: {resp.status_code}") # ───────────────────────────────────────────────────────────────────────────── # LOCUST EVENT HOOKS — Performance thresholds for CI/CD gate # ───────────────────────────────────────────────────────────────────────────── @events.quitting.add_listener def assert_performance_thresholds(environment, **kwargs): """ Fail the Locust run if SLAs are breached. These thresholds act as the CI/CD performance gate. """ stats = environment.runner.stats.total p95 = stats.get_response_time_percentile(0.95) p99 = stats.get_response_time_percentile(0.99) error_rate = stats.fail_ratio print("\n" + "=" * 60) print("📊 FASHIONISTAR AUTH STRESS TEST — RESULTS") print("=" * 60) print(f" Total Requests : {stats.num_requests:,}") print(f" Total Failures : {stats.num_failures:,}") print(f" Error Rate : {error_rate:.2%}") print(f" p50 Latency : {stats.get_response_time_percentile(0.5):.0f}ms") print(f" p95 Latency : {p95:.0f}ms") print(f" p99 Latency : {p99:.0f}ms") print(f" Peak RPS : {stats.total_rps:.0f}") print("=" * 60) # ── SLA Gates ─────────────────────────────────────────────────────────── if error_rate > 0.05: # >5% error rate = FAIL print(f"❌ FAIL: Error rate {error_rate:.2%} exceeds 5% threshold") environment.process_exit_code = 1 if p95 and p95 > 2000: # p95 > 2s = WARN (not hard fail for local) print(f"⚠️ WARN: p95 latency {p95:.0f}ms exceeds 2000ms threshold") if p95 and p95 <= 2000 and error_rate <= 0.05: print("✅ PASS: All SLA thresholds met") environment.process_exit_code = 0