gdpr-test-patterns
Reference catalog of GDPR-aligned test patterns - data-subject-rights workflows (Art. 15 access, Art. 16 rectification, Art. 17 erasure / "right to be forgotten", Art. 18 restriction, Art. 20 portability, Art. 21 objection); consent recording + revocation per Art. 7; data-residency assertions per Art. 44 - 50 international transfers; breach-notification timing tests per Art. 33 (72 hours); data-minimization assertions in fixtures per Art. 5(1)(c). The California analogue - CCPA/CPRA patterns by Cal. Civ. Code section, including Global Privacy Control (GPC) opt-out, right-to-know, deletion, right-to-correct, and sensitive-PI limits - lives in references/ccpa.md. Use when authoring GDPR- or CCPA/CPRA-readiness tests for any product processing EU or California personal data.
Install with skills.sh (any agent)
npx skills add testland/qa --skill gdpr-test-patternsgdpr-test-patterns
Overview
Per gdpr.eu (opens in new window) (community-maintained reference; canonical text at eur-lex.europa.eu Regulation 2016/679):
GDPR (General Data Protection Regulation, in force 2018-05-25) applies to any processing of EU personal data regardless of the processor's location. Failure to demonstrate compliance carries fines up to €20M or 4% of global annual turnover (whichever higher).
This is a reference skill - defines the test-pattern catalog by Article. Tests use the team's existing test framework; this skill is the per-Article test recipe.
For products also processing California consumer data, the CCPA/CPRA catalog by Cal. Civ. Code section - GPC opt-out (§1798.135), right-to-know, deletion, right-to-correct, sensitive-PI limits - is in references/ccpa.md.
When to use
How to use
Test patterns by GDPR Article
Art. 7 - Conditions for consent
def test_consent_recorded_at_collection_time():
response = client.post('/signup', json={
'email': 'alice@example.com',
'consent_marketing': True,
'consent_timestamp': '2026-05-06T12:00:00Z',
})
user = User.objects.get(email='alice@example.com')
consent = ConsentRecord.objects.get(user=user, scope='marketing')
assert consent.granted is True
assert consent.granted_at is not None
assert consent.granted_via == 'signup-form' # auditable evidence
def test_consent_revocable():
revoke_consent(user, scope='marketing')
consent = ConsentRecord.objects.get(user=user, scope='marketing')
assert consent.granted is False
assert consent.revoked_at is not None
# Subsequent marketing emails must not be sent:
assert not user.is_eligible_for(EmailType.MARKETING)Art. 15 - Right of access
def test_subject_access_request_returns_all_personal_data():
response = authenticated_client.post('/sar', json={'subject_email': 'alice@example.com'})
assert response.status_code == 200
data = response.json()
# Must include data from EVERY system that holds PII for this subject:
assert 'profile' in data
assert 'billing' in data
assert 'support_tickets' in data
assert 'analytics' in data # often forgotten; tests catch it
assert 'third_party_sharing' in data
# Response must arrive within 1 month per Art. 12(3):
assert response.headers['X-Processing-Time'] < timedelta(days=30)Art. 17 - Right to erasure ("right to be forgotten")
def test_erasure_removes_all_personal_data():
erase_subject('alice@example.com')
# Across ALL systems:
assert User.objects.filter(email='alice@example.com').count() == 0
assert BillingRecord.objects.filter(user_email='alice@example.com').count() == 0
assert AnalyticsEvent.objects.filter(user_email='alice@example.com').count() == 0
# Backup retention: erasure marker recorded; backup expires within retention window
assert ErasureMarker.objects.filter(subject='alice@example.com').exists()Art. 20 - Right to data portability
def test_data_portability_export_machine_readable():
response = client.post('/data-export', json={'subject': 'alice@example.com'})
export = response.json()
# Must be in "structured, commonly used and machine-readable format"
assert response.headers['Content-Type'] in ['application/json', 'text/csv', 'application/xml']
# Format must be self-describing (keys not random IDs):
assert 'profile' in export
assert 'orders' in exportArt. 33 - Breach notification (72-hour test)
def test_breach_notification_workflow_within_72h():
# Simulate a breach detection event
breach = BreachIncident.create(detected_at=timezone.now())
# Workflow MUST notify supervisory authority within 72 hours:
deadline = breach.detected_at + timedelta(hours=72)
notification = BreachNotification.objects.filter(incident=breach).first()
assert notification is not None
assert notification.sent_at <= deadline
assert notification.recipient == 'supervisory.authority@dpa.example.eu'Art. 44 - 50 - International transfers (data residency)
def test_eu_user_data_stored_in_eu_region():
user = User.objects.create(email='alice@example.fr', region='EU')
# Storage assertions vary by infra (AWS region, GCP region, etc.):
assert user.storage_region in ['eu-west-1', 'eu-central-1', 'eu-north-1']
# Cross-region replication MUST stay in EU:
backups = Backup.objects.filter(user=user)
for b in backups:
assert b.region in EU_REGIONSArt. 5(1)(c) - Data minimization
def test_signup_fixtures_contain_only_required_pii():
fixture = load_fixture('user_signup.json')
required = {'email', 'consent_terms', 'consent_marketing'}
optional = {'phone', 'city', 'date_of_birth'}
for k in fixture.keys():
assert k in required | optional, f"Unrecognized field: {k}"
# No SSN, no passport number, no genetic / biometric data unless explicitly justified:
forbidden = {'ssn', 'passport', 'genetic_data', 'biometric_data'}
for k in forbidden:
assert k not in fixture, f"Forbidden PII type: {k}"Worked example
A support agent triggers an Art. 17 erasure for alice@example.com. The team already has test_erasure_removes_all_personal_data (above), which asserted the app User table was empty and passed. Running the multi-system version reveals AnalyticsEvent still holds rows keyed by user_email, because analytics writes were never wired into the erasure job. The test fails on AnalyticsEvent.objects.filter(...).count() == 0. The team adds analytics to the erasure fan-out, re-runs, and the test goes green with an ErasureMarker recorded so the nightly backup expiry honors the request within the retention window.
Key compliance gaps tests should catch
| Gap | Detection |
|---|---|
| Marketing emails sent to revoked-consent users | Step Art. 7 + email-flow tests |
| SAR returns incomplete dataset (missing analytics/CRM) | Step Art. 15 multi-system assertion |
| Erasure leaves data in unindexed backup tables | Step Art. 17 multi-system assertion |
| EU user data quietly replicated to US region | Step Art. 44 - 50 region assertion |
| Breach detected but DPO not notified within 72h | Step Art. 33 timing test |
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Test only main-app data store on SAR/erasure | Misses analytics, CRM, support, backups | Multi-system assertion (Steps Art. 15 + 17) |
| Hardcode 30-day SAR window assumption | GDPR allows extension to 3 months in complex cases | Test timeline against actual policy doc |
| Test consent recording without revocation | Half the workflow uncovered | Both grant + revoke tests (Step Art. 7) |
| Fixture data uses real customer PII | GDPR violation in tests themselves | Use synthetic-pii-generator |
| Skip data-minimization assertions | New PII types creep in via schema changes | Step Art. 5(1)(c) field-allowlist test |
Limitations
References
CCPA / CPRA test patterns
View source (opens in new window)CCPA / CPRA test patterns
Companion reference for gdpr-test-patterns - the California analogue of the per-Article GDPR catalog, organized by Cal. Civ. Code section.
Overview
Per oag.ca.gov/privacy/ccpa (opens in new window) (California Attorney General authoritative source):
CCPA (California Consumer Privacy Act, in force 2020-01-01) and CPRA (California Privacy Rights Act, in force 2023-01-01) apply to businesses meeting one of three thresholds:
Failure carries civil penalties up to $7,500 per intentional violation + $2,500 per non-intentional violation; private right of action for data breaches.
This reference defines the test-pattern catalog by Cal. Civ. Code section. The host skill gdpr-test-patterns covers the EU side of the international compliance footprint.
When to use
Test patterns by Cal. Civ. Code section
§1798.135 - Right to opt out via Global Privacy Control (GPC)
CPRA mandates honoring the GPC browser signal (per globalprivacycontrol.org (opens in new window)):
def test_gpc_signal_blocks_sale_and_share():
response = client.get('/page', headers={'Sec-GPC': '1'})
# Page MUST treat the visitor as opted-out:
assert 'do-not-sell-cookie' in response.cookies
# Tracking pixels for sale/share must NOT fire:
assert 'analytics-share.js' not in response.text
assert 'ads-third-party.js' not in response.text
# Status MUST be recorded for compliance evidence:
assert OptOutRecord.objects.filter(visitor_id=session_id).exists()§1798.110 - Right to know (categories of PI collected)
def test_consumer_request_returns_all_pi_categories():
response = client.post('/privacy/right-to-know', json={
'consumer_email': 'alice@example.com'
})
body = response.json()
# CCPA Cat. 11 personal info categories:
expected = {
'identifiers',
'commercial_info',
'biometric_info',
'internet_activity',
'geolocation_data',
'professional_or_employment',
'inferences',
}
# Test asserts every category present in response (NULL acceptable):
for cat in expected:
assert cat in body§1798.105 - Right to delete
def test_deletion_request_completes_within_45_days():
request = DeletionRequest.create(consumer_email='alice@example.com')
# CCPA: 45 days, extendable by 45 more (90 max) with notice
deadline = request.received_at + timedelta(days=45)
completion = DeletionRequest.objects.get(id=request.id)
assert completion.status == 'completed'
assert completion.completed_at <= deadline§1798.121 - Right to limit sensitive PI use (CPRA)
def test_limit_sensitive_pi_use():
# Consumer requests limit on sensitive PI (per CPRA SPI categories)
submit_limit_request(consumer='alice@example.com')
# Subsequent processing MUST NOT use SPI for inference / advertising:
response = client.get('/recommendations', headers={'X-Consumer-Email': 'alice@example.com'})
# Recommendations engine MUST NOT use SPI (geolocation, racial, religious, etc.):
used_features = response.json()['feature_attribution']
forbidden_spi = {'precise_geolocation', 'racial_ethnic', 'religious', 'union_membership'}
for feature in used_features:
assert feature not in forbidden_spi§1798.106 - Right to correct (CPRA)
def test_correction_request_updates_records_within_45_days():
submit_correction(consumer='alice@example.com', field='name', value='Alice Smith')
deadline = timezone.now() + timedelta(days=45)
# All systems must reflect the correction:
assert User.objects.get(email='alice@example.com').name == 'Alice Smith'
assert BillingRecord.objects.get(user_email='alice@example.com').name == 'Alice Smith'
# Third parties that received the prior incorrect value must be notified:
assert NotificationLog.objects.filter(
type='correction_propagation',
recipient='third-party-vendor@example.com',
).exists()§1798.130 - Notice requirements
def test_privacy_policy_disclosed_at_collection():
# Every PI collection point must disclose categories + purposes
response = client.get('/signup')
assert 'privacy-notice' in response.text
assert any(link in response.text for link in [
'/do-not-sell-or-share',
'/limit-sensitive-pi-use',
])
# Disclosed categories MUST match what's actually collected
disclosed = parse_privacy_notice(response.text)
actual = analyze_signup_form(response.text)
assert disclosed >= actual # disclosure is superset of collectionSensitive Personal Information categories (CPRA §1798.140(ae))
| Category | Examples |
|---|---|
| Government IDs | SSN, driver's license, passport, alien registration |
| Account login + credentials | Username + password / security questions |
| Precise geolocation | <1850 ft / 564 m radius |
| Racial / ethnic origin | Self-reported demographics |
| Religious / philosophical beliefs | Religious affiliation |
| Union membership | Trade union status |
| Communication content | Email body, SMS body, message content |
| Genetic data | DNA test results |
| Biometric for unique identification | Faceprint, voiceprint, fingerprint |
| Health info | Medical history, medications |
| Sexual orientation / sex life | Self-reported orientation |
Test patterns above (§1798.121) protect these categories specifically.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Honor opt-out cookie but ignore GPC header | CPRA mandates GPC support | Step §1798.135 GPC test |
| Right-to-know returns only main app data | Misses analytics, CRM | Multi-system response (Step §1798.110) |
| Correction propagated only locally | Third parties retain incorrect data | Notification log assertion (Step §1798.106) |
| SPI categorical restriction tested only at API edge | Inference engines bypass | Feature-attribution-level test (Step §1798.121) |
| Test fixtures use real California PII | CCPA violation in tests | synthetic-pii-generator |
Limitations
References
Related skills
audit-trail-test-author
Build-an-X for audit-log tests across compliance frameworks - required-events catalog (auth events / privilege change / data access / admin action / config change / export / impersonation); structured-log-format assertions per OWASP A09:2021; tamper-evident chain (hash-chain + signed-batch patterns) for HIPAA §164.312(b) + PCI Req 10 + SOC 2 CC7.3; immutability + retention per framework; query-replay tests for forensic reconstruction. Use when authoring audit log tests for any compliance framework (HIPAA / PCI / SOC 2 / GDPR / etc.).
compliance-coverage-scoring
Scores existing tests and evidence against a named compliance framework's criteria list (GDPR, CCPA/CPRA, SOC 2 Trust Services Criteria, HIPAA Security Rule, PCI DSS, ISO/IEC 27001), marking every criterion met, partial, not met, or not applicable with a stated evidence requirement per state, and recording each scope exclusion with its criterion reference, reason, named approver, and re-review date. Includes an adversarial readiness-review mode with hard refusal rules (never "ready" with an unjustified gap), and the ISO/IEC 27001:2022 Annex A per-control test-pattern catalog in references/iso27001.md. Produces a readiness self-assessment only: not certification, not an audit opinion, not legal advice. Use when a framework version has been named and an evidence set already exists, and someone needs a per-criterion readiness score before an observation period opens, before a qualified assessor arrives, or in response to a regulator inquiry.
hipaa-test-patterns
Reference catalog of HIPAA Security Rule-aligned test patterns - administrative safeguards (45 CFR §164.308: workforce training, access management, contingency planning), physical safeguards (§164.310: facility access, workstation security, device disposal), technical safeguards (§164.312: access control, audit logs, integrity, transmission security); PHI handling assertions in fixtures; minimum-necessary tests per §164.502(b); BAA-scope boundary verification. Use when authoring HIPAA-readiness tests for any product handling Protected Health Information.
pci-dss-control-test-author
Build-an-X for PCI DSS v4.0 scope verification - cardholder data environment (CDE) boundary tests, segmentation tests (PCI Req 1), prohibited-data-storage assertions per Req 3 (no full track data, no CVV/CAV2/CVC2/CID, no PIN/PIN block post-authorization), key-management tests per Req 3.6, encryption-of-transmissions per Req 4; includes the scope catalog (SAQ A / A-EP / D levels, PAN-storage rules, hosted-fields / tokenization scope-reduction patterns) in references/pci-scope.md. Use when authoring PCI DSS scope-reduction + control tests for any system handling payment-card data, or when determining a payment integration's SAQ level.
soc2-evidence-collector
Build-an-X for SOC 2 Type II evidence collection and auditor-facing packaging - per-Trust-Services-Criterion test artifacts (Common Criteria CC1.1 - CC9.2; plus Availability A1, Confidentiality C1, Processing Integrity PI1, Privacy P1 - P9 if in scope); auto-collection from CI logs + audit trails + access logs + change-management records; alignment with Vanta / Drata / Secureframe evidence shapes; observation-period sampling. Cross-framework evidence packaging (control-evidence matrix, timestamped bundles, chain-of-custody notes per NIST SP 800-72 - also for ISO 27001 / HIPAA / PCI DSS / GDPR / FedRAMP) lives in references/evidence-packaging.md. Use when the team is preparing for SOC 2 Type II audit and needs continuous evidence collection, or when any audit engagement requires auditor-ready evidence packages built from automated test output.