Healthcare QA
Patient-safety-critical testing: EHR accuracy, HIPAA compliance, and clinical workflow integrity.
// OVERVIEW
Healthcare applications fail at the intersection of consent, isolation, and clinical accuracy. The most dangerous bugs are invisible: a withdrawn consent that is still honoured, a record returned for the wrong patient, or a clinical value accepted without validation — none raise a visible error, but all are compliance breaches or patient-safety risks.
// What makes Healthcare QA different
- Consent is a lifecycle, not a flag — a consent can be granted, withdrawn, or expired, and a withdrawn consent that continues to be honoured is a HIPAA-class bug, not a UX issue
- Cross-patient row-level isolation must be enforced at the data layer: a query by patient ID with an insufficient WHERE clause returns another patient's record without raising any error
- Clinical data accuracy is a safety concern: impossible values (a future date of birth, a negative dosage, an out-of-range lab result) must be rejected as validation errors, not accepted and stored
- Audit log completeness is legally mandated: every read of a patient record must produce an immutable log entry — a read that leaves no audit trail is a compliance breach regardless of whether the data was misused
- Accessibility is first-class: an inaccessible intake form means a patient cannot complete their own health record — this is a functional failure, not a nice-to-have
// Core user journeys
| Journey | What to cover |
|---|---|
| Patient record view and create | Authenticated clinician or patient-portal user views or creates a patient record; assert the correct record is returned and an audit log entry is written |
| Consent lifecycle | Grant consent for a third-party integration, withdraw it, and assert the data-layer access check honours the withdrawal immediately without relying on a cache flush |
| Clinical file upload | Upload a lab result, referral letter, or imaging file; assert correct storage, access control, and that the filename is sanitised before writing to the storage layer |
| Role-based record access | Clinician, admin, and patient-portal user each attempt to access clinician-only and patient-only record sections; assert correct allow or deny for each role |
| Appointment scheduling and reminder | Book an appointment, assert confirmation is sent, and assert no reminder fires for a patient who has opted out of all communications |
// RISKS & TEST AREAS
// Main risk areas
| Risk | Why it matters |
|---|---|
| Cross-patient data leak | A record query with an insufficient WHERE clause or off-by-one ID error returns another patient's record to the authenticated user without any visible error or exception |
| Withdrawn consent not enforced at data layer | A patient revokes consent for a third-party integration; the revocation is stored but the data-access check reads from a 24-hour cache, so the third party continues receiving the patient's data until the cache expires |
| Audit log gap on bulk-export path | Individual record reads are logged correctly, but a bulk-export endpoint calls the storage layer directly and bypasses the per-record logging middleware, producing a batch of reads with no audit entries |
| Impossible clinical value accepted | A date of birth submitted as 2099-01-01, a negative dosage, or an out-of-range lab value is accepted with HTTP 201 and stored — no validation error is raised at the API or database layer |
| Role bypass via direct URL | A patient-portal authenticated user navigates directly to a clinician-only endpoint URL; the server returns the restricted content without checking the authenticated user's role |
// Functional areas to test
- Patient record CRUD: create, view, update, and delete with correct permission checks on every operation
- Consent lifecycle management: grant, withdraw, and expire consent; assert access checks reflect the current state immediately
- File upload: clinical documents (lab results, referral letters, imaging); filename sanitisation, content-type validation, and per-role access control
- Role-based access control: clinician, admin, patient-portal, and auditor roles each with distinct record-section permissions
- Audit log: immutable timestamped entry for every record read, write, and export, associated with the authenticated user ID and action type
- Appointment scheduling and notification: booking, confirmation, reminder suppression on opt-out, and cancellation
// API & integration areas
- HL7/FHIR record read and write endpoints: assert correct patient scoping on every response, including paginated results and bulk operations
- External lab result ingestion: assert lab results are associated with the correct patient record and trigger the expected clinician notification
- Consent status API: assert the endpoint returns the current state (granted, withdrawn, expired) and that data-access endpoints honour it without delay
- Document storage API: assert upload returns a storage reference, download enforces per-role access control, and deletion produces an audit entry
- Notification and reminder API: assert appointment reminders are suppressed for patients with communication opt-out and that suppression takes effect immediately after opt-out
// Data testing
- Never use real patient data (PHI) in test environments — use synthetic patient records with realistic but entirely fabricated identifiers
- Seed synthetic patients with known consent states: granted, withdrawn, expired, and never-granted — one patient per state for targeted consent-lifecycle tests
- Seed edge-case clinical values: the minimum valid value, maximum valid value, and at least one impossible value (future DOB, negative dosage, above-range lab result) per field under test
- Seed a full role matrix: at least one account for each of clinician, admin, patient-portal user, and auditor for RBAC and audit-log tests
// CROSS-CUTTING CONCERNS
// Security & privacy
- PHI must never appear in application logs, error messages, stack traces, or analytics payloads — scan test-run logs after every patient-record operation to confirm
- Row-level patient isolation: every record query must be scoped to the authenticated user's permitted patient list; verify by querying as patient A using patient B's record ID
- Consent check must occur at the data layer, not only in the UI or API middleware — a direct service-account call must also honour the current consent state
- Audit log immutability: assert that no UPDATE or DELETE operation exists on the audit log table; log records must be append-only and must not be modified after creation
// Accessibility
- WCAG AA contrast and full keyboard navigation on all patient intake forms and clinical data entry screens
- Error-state focus management: when a clinical field fails validation (e.g. impossible date of birth), focus must move to the first failing field and the error must be associated via aria-describedby
- Screen reader on dynamic record-update confirmations and consent-status changes: assert changes are announced via an ARIA live region, not silently reflected in the DOM
// Performance
- Patient record search under concurrent portal load: assert search latency stays within the defined SLA when multiple patient-portal users query simultaneously
- Large document upload (DICOM imaging files): assert the endpoint handles files up to the defined size limit and returns a clear, specific error for files above it
- Audit log query at 12-month volume: assert filtering by patient ID and date range returns results within the defined response-time threshold at realistic data volume
// Mobile & responsive
- Patient portal at 375 px: date pickers for date of birth and appointment booking must be touch-usable, and all form fields must meet WCAG minimum tap-target size
- File upload from mobile camera: assert the upload input accepts camera capture, the resulting file is stored correctly, and a realistic-size mobile photo does not exceed the upload size limit
// BUGS & SCENARIOS
// Common bugs
| Bug | Scenario / repro |
|---|---|
| Cross-patient record leak | Clinician queries for patient ID 123; an off-by-one in the record-fetch query returns patient 124's record — no error is raised and the clinician sees another patient's full record |
| Withdrawn consent ignored | Patient revokes consent for a third-party integration via the portal; the revocation is written to the consent table but the access check caches consent state for 24 hours; the third party continues receiving the patient's data until the cache expires |
| Audit log gap on bulk export | Admin triggers a bulk export of 500 patient records; the export completes successfully but writes no audit entries because the bulk-export path calls the storage layer directly and bypasses the per-record logging middleware |
| Impossible date of birth accepted | A new patient record is submitted with date_of_birth = '2099-01-01'; the API returns HTTP 201 and the record is saved with a future birth date — no validation error is raised at the API or the database layer |
| Role bypass via direct URL | A patient-portal authenticated user navigates directly to /patients/456/clinical-notes; the server renders the clinician-only note without checking whether the authenticated user holds the clinician role |
// Example test scenarios
- 01Create two patient records; authenticate as patient A; request patient B's record by ID — assert the response is HTTP 403 or 404, not patient B's data
- 02Grant consent for integration X as patient A; withdraw the consent; call integration X's data-access endpoint — assert the response is denied or revoked status, not the patient's data
- 03Authenticate as a clinician; read a patient record; assert the audit log contains a timestamped entry with the clinician's user ID, action type READ, and the patient record ID — no read must be unlogged
- 04Submit a new patient record with date_of_birth set to a date 50 years in the future — assert the API returns HTTP 422 with a clinical-validation error, not HTTP 201
- 05Authenticate as a patient-portal user; attempt GET on the clinician-notes endpoint for any patient ID — assert the response is HTTP 403, not the clinical notes
// Edge cases
- Consent withdrawn mid-transaction: a data-access request starts processing before the revocation commits — assert the in-flight behaviour is documented and deterministic (either complete under the original state or reject atomically)
- Patient record merge: admin merges two records for the same patient into one surviving record — assert the audit trail from both source records is preserved and linked to the surviving record ID
- PHI in uploaded filename: a referral PDF submitted with the patient's name in the filename — assert the filename is sanitised or replaced with a system reference before storage, and the original name does not appear in any log
- Appointment reminder sent after opt-out: patient opts out of all communications after an appointment is already scheduled — assert the pending reminder is suppressed, not delivered
- Clinician account deactivated mid-session: a form submission is in-flight at the moment the account is deactivated — assert the submission is rejected with an authentication error, not silently accepted and stored
// AUTOMATION & TOOLS
// What to automate
- Cross-patient isolation suite: parametrised test iterating over patient ID pairs, asserting that each authenticated user receives only their own record and never another patient's data via any query path
- Consent lifecycle automation: grant→withdraw→assert denied; grant→expire→assert denied; never-granted→assert denied — three distinct consent states each verified against the live data-access endpoint
- Audit log coverage check: after every test-run operation, assert that each record-read and record-write produced a corresponding audit entry with the correct user ID and action type, including the bulk-export path
- RBAC matrix sweep: automated test across all patient-record endpoints and all four roles (clinician, admin, patient-portal, auditor), asserting the correct allow or deny result for every combination
// Useful tools
PostmanFHIR/HL7 API collections, consent endpoint verification, and bulk-export audit scenario testingAccessibility checkerValidate patient intake and clinical data-entry forms against WCAG AA automaticallyColor contrast checkerVerify clinical UI contrast ratios under both light and dark themesHow to test: file uploadStructured test scenarios for clinical document upload, content-type validation, and access controlHow to test: role-based accessClinician, admin, and patient-portal RBAC coverage including direct-URL bypass and horizontal escalation checksRole & permission testing checklistChecklist covering role-inheritance gaps, privilege escalation, and consent-enforcement paths
// SHIP & LEARN
// Release readiness checklist
- Cross-patient isolation suite passes: no authenticated user can retrieve another patient's record through any query path
- Consent withdrawal enforced at the data layer: revocation is reflected immediately in data-access checks across all integration paths, not only in the UI
- Audit log coverage verified: every record-read and record-write path — including bulk export — produces a log entry with the correct user ID and action type
- Impossible clinical values rejected: future date of birth, negative dosage, and out-of-range lab values each return HTTP 422 in all input paths
- Role bypass blocked: direct URL access to clinician-only endpoints returns HTTP 403 for patient-portal and unauthenticated users
- Accessibility audit passed: all patient intake and clinical data-entry forms meet WCAG AA contrast and keyboard navigation requirements
- PHI log scan clean: no patient identifiers or clinical data appear in application logs, error payloads, or analytics events after any test-run operation