feat: initial public release

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.
This commit is contained in:
James Cottrill
2026-04-13 14:20:15 +00:00
commit fbf26453f2
341 changed files with 62807 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
"""Tests for organisation and user CRUD endpoints and schemas."""
import uuid
import pytest
from pydantic import ValidationError
from src.schemas.organisation import OrganisationCreate, OrganisationResponse, OrganisationUpdate
from src.schemas.user import UserCreate, UserResponse, UserRole, UserUpdate
class TestOrganisationSchemas:
def test_create_valid(self):
org = OrganisationCreate(name="Acme Corp", slug="acme-corp")
assert org.name == "Acme Corp"
assert org.slug == "acme-corp"
assert org.billing_plan == "free"
def test_create_invalid_slug(self):
with pytest.raises(ValidationError):
OrganisationCreate(name="Acme", slug="INVALID SLUG!")
def test_create_empty_name_rejected(self):
with pytest.raises(ValidationError):
OrganisationCreate(name="", slug="valid-slug")
def test_update_partial(self):
update = OrganisationUpdate(name="New Name")
data = update.model_dump(exclude_unset=True)
assert data == {"name": "New Name"}
assert "contact_email" not in data
def test_response_from_attributes(self):
now = "2026-01-01T00:00:00Z"
resp = OrganisationResponse(
id=uuid.uuid4(),
name="Test",
slug="test",
contact_email=None,
billing_plan="free",
created_at=now,
updated_at=now,
)
assert resp.name == "Test"
class TestUserSchemas:
def test_create_valid(self):
user = UserCreate(
email="test@example.com",
password="securepass123",
full_name="Test User",
)
assert user.role == UserRole.VIEWER
def test_create_short_password_rejected(self):
with pytest.raises(ValidationError):
UserCreate(email="a@b.com", password="short", full_name="Test")
def test_create_invalid_email_rejected(self):
with pytest.raises(ValidationError):
UserCreate(email="not-an-email", password="securepass123", full_name="Test")
def test_create_with_role(self):
user = UserCreate(
email="admin@example.com",
password="securepass123",
full_name="Admin",
role=UserRole.ADMIN,
)
assert user.role == UserRole.ADMIN
def test_update_partial(self):
update = UserUpdate(role=UserRole.EDITOR)
data = update.model_dump(exclude_unset=True)
assert data == {"role": "editor"}
def test_response_from_attributes(self):
now = "2026-01-01T00:00:00Z"
resp = UserResponse(
id=uuid.uuid4(),
organisation_id=uuid.uuid4(),
email="a@b.com",
full_name="Test",
role="viewer",
created_at=now,
updated_at=now,
)
assert resp.role == "viewer"
class TestUserRole:
def test_role_values(self):
assert UserRole.OWNER == "owner"
assert UserRole.ADMIN == "admin"
assert UserRole.EDITOR == "editor"
assert UserRole.VIEWER == "viewer"
def test_invalid_role_rejected(self):
with pytest.raises(ValidationError):
UserCreate(
email="a@b.com",
password="securepass123",
full_name="Test",
role="superadmin",
)
@pytest.mark.asyncio
class TestRoutesRegistered:
async def test_org_routes(self, client):
response = await client.get("/openapi.json")
paths = response.json()["paths"]
assert "/api/v1/organisations/" in paths
assert "/api/v1/organisations/me" in paths
async def test_user_routes(self, client):
response = await client.get("/openapi.json")
paths = response.json()["paths"]
assert "/api/v1/users/" in paths
assert "/api/v1/users/{user_id}" in paths
async def test_org_endpoints_require_auth(self, client):
response = await client.get("/api/v1/organisations/me")
assert response.status_code == 401
async def test_user_endpoints_require_auth(self, client):
response = await client.get("/api/v1/users/")
assert response.status_code == 401
async def test_create_org_rejects_invalid_body(self, client, monkeypatch):
"""Create org endpoint validates the request body schema.
We need to enable the bootstrap token first so the request
reaches the body-validation stage (the token guard otherwise
fires before Pydantic validation and we'd see 403 instead).
"""
from src.config.settings import get_settings
monkeypatch.setattr(get_settings(), "admin_bootstrap_token", "test-token")
response = await client.post(
"/api/v1/organisations/",
json={"name": "", "slug": "INVALID SLUG!"},
headers={"X-Admin-Bootstrap-Token": "test-token"},
)
assert response.status_code == 422