Skip to main content

How to Test Audit Logs and Compliance Trails

Short answer

Compliance fails when privileged actions leave no immutable audit row, logs omit actor or target ids, retention deletes too early, or tamper attempts succeed—not when an admin page lists events. Probe audit entries within seconds of each sensitive action, map audit_event_type scenarios to SOC2/PCI control matrices via requirement traceability, and expand actor_role coverage with TrueCoverage and /testchimp evolve.

Part of Testing Guides by industry.

Who this is for

Teams subject to SOC2, PCI-DSS, HIPAA audit controls, or enterprise procurement requiring who-did-what-when trails for admin actions, privilege changes, data exports, logins, and configuration updates.

Why testing audit trails matters

"If it's not in the audit log, compliance fails" is literal in audits:

  • Missing privilege escalation log — admin role granted without record; SOC2 Type II finding.
  • Incomplete actor attribution — service account vs human unclear; incident response blocked.
  • Mutable logs — admin deletes audit rows; destroys trust in trail.
  • Retention violation — logs purged before 1-year policy; regulatory breach.
  • DSAR actions unlogged — GDPR export/delete without audit; pairs with GDPR guide.

E2E proves integration: action → log row appears with correct audit_event_type, actor_id, target_id, timestamp—not exhaustive log schema unit tests alone.

Complexity map

ScenarioEdge caseWhy tests breakApproach
Missing eventAdmin delete userNo log rowProbe within 5s
Wrong actorImpersonationActor mismatchProbe actor_id
Service accountAutomated jobHuman expectedProbe actor_type
Bulk actionSingle aggregated logMissing detailProbe payload ids
Failed actionLogin failShould still logProbe failure event
Log tamperDelete audit rowShould failAPI negative 403
RetentionOld row goneToo earlyclock + probe exists
Cross-tenantTenant A actionWrong tenant_idProbe scope
Break-glassEmergency accessMust log reasonProbe reason field
Export DSARLarge jobMissing start/endProbe dsar events
Config changeFeature flagSilent changeProbe config diff
Clock skewTimestamp orderAudit doubtNTP + probe monotonic

Probe pattern after sensitive action

const admin = await request.post('/api/test/seed-admin', {
data: { runId, adminId: `admin-${runId}` },
}).then(r => r.json());

await loginAs(page, admin.token);
await page.goto('/admin/users');
await page.getByRole('button', { name: 'Delete user' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();

await expect.poll(async () => {
const logs = await request.get(
`/api/test/audit?runId=${runId}&event= user_deleted`
).then(r => r.json());
return logs.entries.length;
}, { timeout: 10_000 }).toBeGreaterThan(0);

const entry = logs.entries[0];
expect(entry.actorId).toBe(admin.adminId);
expect(entry.targetUserId).toBe(`user-${runId}`);
expect(entry.tenantId).toBe(admin.tenantId);
expect(entry.immutable).toBe(true);

Tune event names to your schema (user.deleted, USER_DELETE, etc.).

Privilege change (SOC2 CC6)

await page.getByLabel('Role').selectOption('super_admin');
await page.getByRole('button', { name: 'Save' }).click();
const logs = await request.get(`/api/test/audit?runId=${runId}&event=role_changed`).then(r => r.json());
expect(logs.entries[0].metadata.previousRole).toBe('admin');
expect(logs.entries[0].metadata.newRole).toBe('super_admin');

Map to control ids in markdown test plans with // @Scenario: CC6.3-role-change.

Tamper resistance negative

const deleteAudit = await request.delete(`/api/test/audit/entry/${entryId}`);
expect(deleteAudit.status()).toBe(403);
const still = await request.get(`/api/test/audit/entry/${entryId}`);
expect(still.status()).toBe(200);

Requirement traceability matrix

Build compliance matrix in markdown:

Controlaudit_event_typeSmartTest scenario id
CC6.2role_changedCC6-ROLE-01
CC7.2user_deletedCC7-DEL-01
Art.15dsar_export_completedGDPR-EXP-01

Link SmartTests with // @Scenario: comments—auditors trace automated proof to controls (requirement traceability).

Requirement slices to cover

  • audit_event_type — role_changed, user_deleted, login_failed, dsar_export, config_updated
  • actor_role — admin, support, system, break_glass

Run /testchimp evolve when prod shows privilege change events but tests lack role_changed coverage.

CI checklist

  1. Probe audit after every sensitive admin spec in same test
  2. Actor and target ids match Arrange seeds
  3. Failed login audit spec (no credential in log payload)
  4. Tamper delete audit returns 403
  5. DSAR export/delete paired with GDPR specs
  6. No PII beyond ids in audit probe responses in traces

Anti-patterns

Anti-patternWhy it failsBetter approach
UI audit table onlyUI not sourceProbe API
wait 30s fixedSlow/flakyexpect.poll 5-10s
Skip failed loginSOC2 gapfailure event probe
Log password fieldsPCI violationRedact in app + test
One admin actionMatrix incompleteTraceability map
Mutable test logsFalse confidenceimmutability negative

Example scenario

Situation: Support agent grants super_admin to user; auditor expects immutable role_changed entry.

Expected outcome: Audit row with actor support id, target user, previous/new roles within 5s.

Why UI-only automation breaks: UI toast success but audit empty—SOC2 observation.

  1. Arrange: Seed support agent and target user with role admin.
  2. Act: Support changes role to super_admin in admin console.
  3. Assert: Probe role_changed entry fields; tamper delete audit 403; user probe has new role.

TestChimp workflow: Compare audit_event_type × actor_role prod vs test; evolve missing privilege events.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Connect scenarios to your QA workflow

Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).

External references

Frequently asked questions

Which audit_event_types must have scenario coverage for SOC2?

Build matrix mapping controls to events—role_changed, user_deleted, login_failed, dsar_export, config_updated. Link // @Scenario: in SmartTests to markdown plans; use TrueCoverage audit_event_type × actor_role to find gaps—run /testchimp evolve.

How quickly should audit entries appear?

Poll probe within 5-10s of action—async log pipelines should still meet near-real-time compliance expectations in test env.

Should failed actions be audited?

Yes for auth and privilege attempts—probe login_failed and unauthorized_access without storing passwords in log payload.

How do I test log immutability?

Attempt DELETE on audit entry via API as non-system user—expect 403; entry still readable.

How do bulk actions appear in audit?

Probe either single bulk event with id list metadata or one event per target—match product contract; assert not silent.

How does GDPR DSAR relate?

Export/delete specs must probe dsar_export_requested and account_deleted audit events—pair with GDPR guide completeness tests.

Can TrueCoverage prioritize admin events?

Compare prod audit_event_type frequency to test coverage—evolve SmartTests for high-risk undertested events like role_changed or break_glass.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo