Use when a product processing EU or California personal data needs GDPR or CCPA/CPRA readiness tests: data subject access, erasure, portability and objection, consent and withdrawal, data residency, 72-hour breach notice, data minimization, and CCPA rights including Global Privacy Control.
89%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
The canonical home for this skill is testland/gdpr-test-patterns
Per gdpr.eu (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.
synthetic-pii-generator - never real customer PII.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)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)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()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 exportdef 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'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_REGIONSdef 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}"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.
| 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-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 |
synthetic-pii-generator - cross-plugin: safe test fixture generationaudit-trail-test-author - companion: GDPR audit logstestland/qa
aa16752
Canonical home
since Jul 23, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.