Skip to content
bitzorcas
中EN

Guide

GDPR testing, operations, and GA gate

Current evidence, missing contracts, SLOs, metrics, alerting, triage, drills, and commercial-delivery gates.

Last updated

The goal is not to prove that an endpoint returns 200. GDPR tests must prove that requests cannot cross tenants or state machines, that no owner is omitted, that incomplete exports never report completion, and that partial failure can recover safely.

1. Existing evidence

TestProvenNot proven
GdprQueryHandlerTestsconsent/SAR lists call IGdprStore with actor tenant/userroute, permission, effective tenant, detail safety
GdprInfrastructureArchitectureTestsno concrete ORM dependency; fail-closed registrationruntime database and compliance semantics
PortRepositoryParityTestsbasic consent, DSR update, and SysUser anonymization match across ORMscommands, transitions, concurrency, rollback, related data
API Shell smokeunavailable port without databaseproduction readiness
ownership architecture teststwo tables are GDPR owner-localclassification and retention

There are no GDPR command unit tests, HTTP authorization tests, export-file tests, or multi-owner erasure tests.

2. State-machine tests

Use table-driven contracts for every allowed and rejected source status.

Closed erasure transition contract
[Theory]
[InlineData("CoolingOff", true, "Executing")]
[InlineData("Submitted", false, null)]
[InlineData("Executing", false, null)]
[InlineData("Completed", false, null)]
[InlineData("Cancelled", false, null)]
[InlineData("Rejected", false, null)]
public void Execute_transition_is_closed(
string from,
bool expectedAllowed,
string? expectedTo)
{
// Every source status has an explicit expectation, including future enum additions.
var result = ErasureTransitions.TryExecute(
ErasureStatus.Parse(from),
coolingOffExpired: true);
// An allowed path also has exactly one expected target status.
result.IsSuccess.ShouldBe(expectedAllowed);
if (expectedAllowed)
result.Value.ShouldBe(ErasureStatus.Parse(expectedTo!));
}

ErasureTransitions is a target type. Also pass SAR IDs to every erasure handler and erasure IDs to every SAR handler.

3. Tenant and subject tests

Construct four independent values:

  • Actor Tenant;
  • Effective Tenant;
  • Subject Tenant;
  • stored row Tenant.
Operate-as keeps actor and subject separate
using var tenantScope = tenantAccessor.Push(
effectiveTenantId: "tenant-b",
actorTenantId: "tenant-host");
// The operator acts for the target tenant, but the subject belongs to that tenant.
var result = await mediator.Send(
new CreateSarForSubject.Command(
SubjectUserId: "user-b",
RequestId: "sar-b-001"),
cancellationToken);
result.IsSuccess.ShouldBeTrue();
// Assert data ownership, subject identity, and actor evidence independently.
stored.TenantId.ShouldBe("tenant-b");
stored.UserId.ShouldBe("user-b");
stored.ActorTenantId.ShouldBe("tenant-host");

If self-service does not support operate-as, reject it explicitly rather than writing mixed context.

4. Idempotency and concurrency

Run concurrent requests against a real database and prove:

  1. one row for the same tenant/type/RequestId;
  2. an explicit namespace policy across request types;
  3. the same RequestId is valid in different tenants;
  4. two operators cannot complete one request;
  5. repeated withdrawal preserves the first time;
  6. repeated owner steps do not duplicate deletion or notification.

Sequential mock tests cannot prove query-then-insert idempotency.

5. SAR completeness

Build a golden dataset for every contributor:

  • 0, 1, 1,000, 1,001, and multi-page records;
  • stable cursors without duplicates or gaps;
  • owner count matching the frozen manifest;
  • artifact hashes matching files;
  • omission reasons for retained data;
  • compatible schema versions;
  • no ticket when generation fails;
  • one-time, expired, foreign-subject, and foreign-tenant ticket rejection.

The current implementation silently omits audit data after item 1,000. Preserve that as a failing contract until corrected.

6. Erasure evidence

Use fault injection across owners:

FaultRequired outcome
owner A succeeds, B temporarily failsPartial/Retrying, never Completed
legal holdRetained with reason and deadline
external processor times outidempotent retry and escalation
Identity user absentpolicy outcome without cross-tenant disclosure
process ends before commitresume from durable checkpoint
evidence store unavailablehigh-value action fails closed

7. Authorization and feature tests

HTTP tests should create roles from the actual Permission Catalog and call all nine routes. They must catch the current manage versus update/delete mismatch.

Cover:

  • no token returns 401;
  • authenticated without an Allow returns 403;
  • self permission cannot process another subject;
  • operator permission cannot cross Effective Tenant;
  • feature off behaves consistently for HTTP, background, and operator paths;
  • role name admin does not replace required permission.

8. Metrics

Aggregate by tenant hash, request type, status, and owner code. Never put raw UserId or RequestId in labels.

MetricPurpose
gdpr_requests_created_totalintake by type
gdpr_requests_in_statework backlog
gdpr_request_age_secondsoldest open request
gdpr_owner_step_duration_secondsowner latency
gdpr_owner_step_failures_totalretryable/permanent failure
gdpr_export_bytespackage capacity
gdpr_download_ticket_rejections_totalattack/client failure
gdpr_erasure_retained_totallegal hold/retention

The current module emits no dedicated metrics.

9. SLOs and alerts

Legal deadlines belong in organization-approved configuration. Engineering SLOs should reserve a safety margin:

  • request not acknowledged within intake budget;
  • SAR/erasure approaching the configured deadline;
  • expired CoolingOff without operator action;
  • old or over-retried Partial steps;
  • expired export object still present;
  • two Completed evidence records for one request;
  • sudden authorization-denial change;
  • unavailable port or evidence-store readiness failure.

Notifications use internal RecordId and CorrelationId, not names, emails, or export URLs.

10. Triage

noyesyesno

Complaint or alert

Locate DSR by RecordId + tenant

RequestType matches status?

Inspect decision + actor/effective tenant

Inspect steps, manifest, and file evidence

Safe to retry?

Resume with the same idempotency key

Freeze and escalate privacy incident

If ProcessSar returns ProcessingFailed while the row remains Submitted, inspect transaction rollback before changing the row manually.

11. Release drills

Before each commercial release:

  1. complete a multi-page SAR on both ORMs;
  2. attack each processing endpoint with a wrong-type ID;
  3. execute just before, exactly at, and after cooling-off;
  4. inject owner failure, database deadlock, and object-store timeout;
  5. replay a ticket and attempt cross-tenant download;
  6. expire and physically verify package deletion;
  7. exercise operate-as and Host callers;
  8. restore a backup and ensure expired exports do not reappear;
  9. reconstruct one request timeline from evidence;
  10. obtain security, operations, and privacy-owner sign-off.

12. GA blocking gate

  • Every handler checks RequestType and a central transition guard.
  • RequestId uniqueness and concurrency tests pass.
  • Catalog, runtime decisions, and feature behavior agree.
  • Actor, Effective Tenant, and Subject are separate.
  • SAR contributors, full paging, manifest, encrypted file, and ticket exist.
  • Erasure owners, legal hold, checkpoints, and processor evidence exist.
  • Failed state persists instead of disappearing on Result rollback.
  • Identity credential/session policy is complete.
  • Classification, retention, destruction, and backup policy are approved.
  • Dual-provider, HTTP, security, recovery, and performance tests pass.
  • Metrics, alerts, runbook, and on-call ownership are ready.
  • Software behavior and organizational legal process pass joint acceptance.

13. Local verification

Terminal window
# Run existing GDPR query, architecture, and provider contracts.
dotnet test tests/BitzOrcas.Application.Tests \
--filter FullyQualifiedName~Gdpr
dotnet test tests/BitzOrcas.Architecture.Tests \
--filter FullyQualifiedName~Gdpr
dotnet test tests/BitzOrcas.Integration.Tests \
--filter FullyQualifiedName~GdprStore_Should_Behave
# Expose production contracts that are not yet implemented.
rg -n "IDataSubjectExportContributor|IDataErasureContributor|LegalHold|ExportManifest" \
src tests -g '*.cs'

Back to GDPR overview

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%