ConsentOS — a privacy-first cookie consent management platform. Self-hosted, source-available alternative to OneTrust, Cookiebot, and CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant architecture with role-based access, configuration cascade (system → org → group → site → region), dark-pattern detection in the scanner, and a tamper-evident consent record audit trail. This is the initial public release. Prior development history is retained internally. See README.md for the feature list, architecture overview, and quick-start instructions. Licensed under the Elastic Licence 2.0 — self-host freely; do not resell as a managed service.
147 lines
4.7 KiB
Python
147 lines
4.7 KiB
Python
"""Load tests for the CMP API using Locust.
|
|
|
|
Run with:
|
|
locust -f tests/load/locustfile.py --host http://localhost:8000
|
|
|
|
Targets:
|
|
- Health check: baseline latency
|
|
- Config fetch: banner script config retrieval (~25KB gzipped target)
|
|
- Consent recording: high-throughput POST endpoint
|
|
- Geo-resolved config: config + GeoIP detection
|
|
"""
|
|
|
|
import uuid
|
|
|
|
from locust import HttpUser, between, task
|
|
|
|
|
|
class BannerScriptUser(HttpUser):
|
|
"""Simulates traffic from the banner script embedded on client websites.
|
|
|
|
This is the highest-volume traffic pattern: every page view triggers
|
|
a config fetch and potentially a consent recording.
|
|
"""
|
|
|
|
wait_time = between(0.1, 0.5)
|
|
|
|
def on_start(self) -> None:
|
|
"""Set up a visitor context."""
|
|
self.visitor_id = str(uuid.uuid4())
|
|
# Use a known test site ID — replace with an actual ID in your environment
|
|
self.site_id = "00000000-0000-0000-0000-000000000001"
|
|
|
|
@task(10)
|
|
def health_check(self) -> None:
|
|
"""Baseline health check — should return in <10ms."""
|
|
self.client.get("/health")
|
|
|
|
@task(30)
|
|
def fetch_config(self) -> None:
|
|
"""Fetch site config — the most common banner script request."""
|
|
self.client.get(
|
|
f"/api/v1/config/sites/{self.site_id}",
|
|
name="/api/v1/config/sites/[site_id]",
|
|
)
|
|
|
|
@task(20)
|
|
def fetch_resolved_config(self) -> None:
|
|
"""Fetch resolved config with region — simulates GeoIP flow."""
|
|
self.client.get(
|
|
f"/api/v1/config/sites/{self.site_id}/resolved?region=EU",
|
|
name="/api/v1/config/sites/[site_id]/resolved",
|
|
)
|
|
|
|
@task(15)
|
|
def fetch_geo_resolved_config(self) -> None:
|
|
"""Fetch geo-resolved config — includes GeoIP detection."""
|
|
self.client.get(
|
|
f"/api/v1/config/sites/{self.site_id}/geo-resolved",
|
|
name="/api/v1/config/sites/[site_id]/geo-resolved",
|
|
headers={"CF-IPCountry": "DE"},
|
|
)
|
|
|
|
@task(5)
|
|
def detect_geo(self) -> None:
|
|
"""Detect visitor region."""
|
|
self.client.get(
|
|
"/api/v1/config/geo",
|
|
headers={"CF-IPCountry": "FR"},
|
|
)
|
|
|
|
@task(20)
|
|
def record_consent(self) -> None:
|
|
"""Record a consent decision — high-throughput POST endpoint."""
|
|
self.client.post(
|
|
"/api/v1/consent/",
|
|
json={
|
|
"site_id": self.site_id,
|
|
"visitor_id": self.visitor_id,
|
|
"action": "accept_all",
|
|
"categories_accepted": [
|
|
"necessary",
|
|
"functional",
|
|
"analytics",
|
|
"marketing",
|
|
"personalisation",
|
|
],
|
|
"categories_rejected": [],
|
|
"gcm_state": {
|
|
"ad_storage": "granted",
|
|
"analytics_storage": "granted",
|
|
"functionality_storage": "granted",
|
|
"personalization_storage": "granted",
|
|
"security_storage": "granted",
|
|
},
|
|
"page_url": "https://example.com/page",
|
|
},
|
|
name="/api/v1/consent/",
|
|
)
|
|
|
|
|
|
class AdminUser(HttpUser):
|
|
"""Simulates admin UI traffic — lower volume, authenticated requests."""
|
|
|
|
wait_time = between(1, 3)
|
|
weight = 1 # Much less traffic than banner scripts
|
|
|
|
def on_start(self) -> None:
|
|
"""Authenticate and get a token."""
|
|
resp = self.client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"email": "admin@test.com",
|
|
"password": "TestPassword123",
|
|
},
|
|
)
|
|
if resp.status_code == 200:
|
|
self.token = resp.json().get("access_token", "")
|
|
self.headers = {"Authorization": f"Bearer {self.token}"}
|
|
else:
|
|
self.token = ""
|
|
self.headers = {}
|
|
|
|
@task(5)
|
|
def list_sites(self) -> None:
|
|
"""List sites in the organisation."""
|
|
self.client.get("/api/v1/sites/", headers=self.headers)
|
|
|
|
@task(3)
|
|
def check_compliance(self) -> None:
|
|
"""Run a compliance check."""
|
|
site_id = "00000000-0000-0000-0000-000000000001"
|
|
self.client.get(
|
|
f"/api/v1/compliance/check/{site_id}?frameworks=gdpr,cnil",
|
|
headers=self.headers,
|
|
name="/api/v1/compliance/check/[site_id]",
|
|
)
|
|
|
|
@task(2)
|
|
def consent_analytics(self) -> None:
|
|
"""Fetch consent analytics summary."""
|
|
site_id = "00000000-0000-0000-0000-000000000001"
|
|
self.client.get(
|
|
f"/api/v1/analytics/summary/{site_id}",
|
|
headers=self.headers,
|
|
name="/api/v1/analytics/summary/[site_id]",
|
|
)
|