CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/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). Use when authoring GDPR-readiness tests for any product processing EU personal data.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files
name:
gdpr-test-patterns
description:
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). Use when authoring GDPR-readiness tests for any product processing EU personal data.

gdpr-test-patterns

Overview

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.

When to use

  • Product processes EU personal data (regardless of processor location).
  • Regulatory audit requires evidence of test coverage for data-subject rights.
  • New feature touches PII handling (signup, profile, billing, analytics, third-party sharing).
  • DPIA (Data Protection Impact Assessment) requires test evidence.

How to use

  1. Inventory every system that stores PII for a data subject (app DB, billing, analytics, CRM, support, backups) - this list drives the multi-system assertions.
  2. Pick the Articles in scope for the feature under test (consent, access, erasure, portability, breach, residency, minimization).
  3. Copy the matching per-Article pattern below and point it at your framework's client + models.
  4. Generate fixture data with synthetic-pii-generator - never real customer PII.
  5. Assert across ALL systems from step 1, not just the main store; add the deadline assertions (SAR 1 month per Art. 12(3), breach 72 hours per Art. 33) where the Article sets one.
  6. Pin every timeline assertion to your written policy doc, not a hardcoded 30-day guess.
  7. Wire the tests into CI so a schema change that reintroduces PII or skips a system fails the build.

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 export

Art. 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_REGIONS

Art. 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

GapDetection
Marketing emails sent to revoked-consent usersStep Art. 7 + email-flow tests
SAR returns incomplete dataset (missing analytics/CRM)Step Art. 15 multi-system assertion
Erasure leaves data in unindexed backup tablesStep Art. 17 multi-system assertion
EU user data quietly replicated to US regionStep Art. 44 - 50 region assertion
Breach detected but DPO not notified within 72hStep Art. 33 timing test

Anti-patterns

Anti-patternWhy it failsFix
Test only main-app data store on SAR/erasureMisses analytics, CRM, support, backupsMulti-system assertion (Steps Art. 15 + 17)
Hardcode 30-day SAR window assumptionGDPR allows extension to 3 months in complex casesTest timeline against actual policy doc
Test consent recording without revocationHalf the workflow uncoveredBoth grant + revoke tests (Step Art. 7)
Fixture data uses real customer PIIGDPR violation in tests themselvesUse synthetic-pii-generator
Skip data-minimization assertionsNew PII types creep in via schema changesStep Art. 5(1)(c) field-allowlist test

Limitations

  • This skill is a test-pattern catalog, not legal advice. GDPR interpretation has nuance per supervisory authority (CNIL, ICO, DPA per country).
  • Tests verify implementation; they don't replace a DPIA or legal review.
  • Article numbers refer to GDPR text; consult eur-lex.europa.eu for canonical wording.
  • Subject-access-request workflow varies per organization; test templates above are starting points.

References

  • gdpr - community reference + Article navigation
  • eur-lex.europa.eu/eli/reg/2016/679/oj - canonical GDPR text
  • edpb.europa.eu - European Data Protection Board guidelines
  • ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/ - UK ICO guide
  • synthetic-pii-generator - cross-plugin: safe test fixture generation
  • ccpa-test-patterns - sister: California analogue
  • audit-trail-test-author - companion: GDPR audit logs
Workspace
testland
Visibility
Public
Created
Last updated
Publish Source
GitHub
Badge
testland/gdpr-test-patterns badge