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.
113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
"""Tests for consent signal validation — mocks Playwright."""
|
|
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from src.consent_validator import (
|
|
_is_tracker_request,
|
|
validate_post_reject,
|
|
validate_pre_consent,
|
|
)
|
|
|
|
|
|
class TestIsTrackerRequest:
|
|
def test_known_tracker(self) -> None:
|
|
assert _is_tracker_request("https://www.google-analytics.com/collect") is True
|
|
|
|
def test_facebook_tracker(self) -> None:
|
|
assert _is_tracker_request("https://connect.facebook.net/en_US/fbevents.js") is True
|
|
|
|
def test_non_tracker(self) -> None:
|
|
assert _is_tracker_request("https://example.com/style.css") is False
|
|
|
|
def test_empty_url(self) -> None:
|
|
assert _is_tracker_request("") is False
|
|
|
|
def test_doubleclick(self) -> None:
|
|
assert _is_tracker_request("https://ad.doubleclick.net/pixel") is True
|
|
|
|
def test_hotjar(self) -> None:
|
|
assert _is_tracker_request("https://static.hotjar.com/c/hotjar.js") is True
|
|
|
|
|
|
class TestValidatePreConsent:
|
|
@pytest.mark.asyncio
|
|
async def test_no_issues_with_only_essential_cookies(self) -> None:
|
|
page = AsyncMock()
|
|
page.evaluate = AsyncMock(return_value={"available": False})
|
|
|
|
context = AsyncMock()
|
|
context.cookies = AsyncMock(return_value=[{"name": "session_id", "domain": "example.com"}])
|
|
|
|
issues = await validate_pre_consent(page, context, {"session_id"}, [])
|
|
assert len(issues) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_non_essential_cookies_flagged(self) -> None:
|
|
page = AsyncMock()
|
|
page.evaluate = AsyncMock(return_value={"available": False})
|
|
|
|
context = AsyncMock()
|
|
context.cookies = AsyncMock(
|
|
return_value=[
|
|
{"name": "session_id", "domain": "example.com"},
|
|
{"name": "_ga", "domain": ".google-analytics.com"},
|
|
{"name": "_fbp", "domain": ".facebook.com"},
|
|
]
|
|
)
|
|
|
|
issues = await validate_pre_consent(page, context, {"session_id"}, [])
|
|
assert len(issues) >= 1
|
|
cookie_issue = next(i for i in issues if i.check == "pre_consent_cookies")
|
|
assert cookie_issue.severity == "critical"
|
|
assert "_ga" in cookie_issue.message
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tracker_requests_flagged(self) -> None:
|
|
page = AsyncMock()
|
|
page.evaluate = AsyncMock(return_value={"available": False})
|
|
|
|
context = AsyncMock()
|
|
context.cookies = AsyncMock(return_value=[])
|
|
|
|
tracker_urls = ["https://www.google-analytics.com/collect?v=1"]
|
|
issues = await validate_pre_consent(page, context, set(), tracker_urls)
|
|
assert len(issues) >= 1
|
|
tracker_issue = next(i for i in issues if i.check == "pre_consent_trackers")
|
|
assert tracker_issue.severity == "critical"
|
|
|
|
|
|
class TestValidatePostReject:
|
|
@pytest.mark.asyncio
|
|
async def test_clean_rejection(self) -> None:
|
|
page = AsyncMock()
|
|
context = AsyncMock()
|
|
context.cookies = AsyncMock(return_value=[])
|
|
|
|
issues = await validate_post_reject(page, context, set(), [])
|
|
assert len(issues) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cookies_after_reject_flagged(self) -> None:
|
|
page = AsyncMock()
|
|
context = AsyncMock()
|
|
context.cookies = AsyncMock(
|
|
return_value=[{"name": "_ga", "domain": ".google-analytics.com"}]
|
|
)
|
|
|
|
issues = await validate_post_reject(page, context, set(), [])
|
|
assert len(issues) >= 1
|
|
assert issues[0].check == "post_reject_cookies"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_trackers_after_reject_flagged(self) -> None:
|
|
page = AsyncMock()
|
|
context = AsyncMock()
|
|
context.cookies = AsyncMock(return_value=[])
|
|
|
|
tracker_urls = ["https://www.google-analytics.com/collect"]
|
|
issues = await validate_post_reject(page, context, set(), tracker_urls)
|
|
assert len(issues) >= 1
|
|
assert issues[0].check == "post_reject_trackers"
|