Skip to content

Alex/happy integration#8145

Open
alexwillingham wants to merge 69 commits into
developfrom
alex/happy-integration
Open

Alex/happy integration#8145
alexwillingham wants to merge 69 commits into
developfrom
alex/happy-integration

Conversation

@alexwillingham

Copy link
Copy Markdown
Contributor

No description provided.

alexwillingham and others added 30 commits June 13, 2026 11:11
Adds one happy-path integration test per zambda for previously
untested executable (http_auth/http_open) zambdas across ehr, patient,
rcm and billing. Each test runs against the local Oystehr backend via
setupIntegrationTest + insertInPersonAppointmentBase, makes no
third-party service calls, and cleans up resources it creates.

Covers reads/config/reports/search and full CRUD lifecycles
(templates, em-codes, quick-picks, in-house-medications, nursing
orders, patient instructions, charge-masters, fee-schedules,
assign/unassign practitioner, service-category/practitioner-role
deletes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers add/edit/remove-payer-from/in-insurance-override-list. These
mutate a single shared override List, so they run sequentially in one
file to avoid optimistic-lock conflicts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…th tests

Covers add-procedure-code, cm-add-procedure-code,
bulk-add-procedure-codes, cm-bulk-add-procedure-codes and
get-version-history. Each creates a fresh fee schedule / charge master
in setup and deletes it afterwards (no shared-resource contention).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers update-fee-schedule, update-charge-master, save-billing-tag and
delete-billing-tag. Each creates its prerequisite resource in setup and
cleans it up afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nate tests

Covers update-procedure-code, delete-procedure-code and
designate-charge-master-entry. Each builds its prerequisite fee
schedule / charge master in setup and cleans it up afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers associate-payer, disassociate-payer, cm-associate-payer and
cm-disassociate-payer. Each creates a fee schedule / charge master and a
throwaway payer Organization in setup and cleans them up afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers find-applicable-fee-schedule, find-applicable-charge-master and
get-charge-master-entry. Each is a reachable lookup returning a
well-formed payload (match may be null when nothing is associated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cm procedure-code tests

The delete-fee-schedule / delete-charge-master zambdas read the resource
id from a body param named `id`, which collides with the SDK
zambda-selector `id` in zambda.execute() — so those cleanup calls were
silently failing and leaking ChargeItemDefinitions. Cleanup now deletes
the ChargeItemDefinition directly via FHIR.

Adds proper happy-path tests for delete-fee-schedule and
delete-charge-master (direct POST with an `id` body), plus
cm-update-procedure-code and cm-delete-procedure-code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Creates an in-house lab set (FHIR List) and toggles its status; the List
is removed afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Creates an immunization order (MedicationAdministration) for a seeded
encounter referencing a real in-house Medication, and cancels it. The
order and Medication are cleaned up afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both are FHIR-backed reads scoped to a seeded appointment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
unlock-appointment clears the lock tag on a seeded appointment;
delete-patient-document removes a DocumentReference created in setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uite

Previously setupIntegrationTest minted (or reused) one M2M client named
after each test file, plus a throwaway Practitioner/Patient profile, and
never cleaned them up — leaving ~one persistent client + profile per
test file on the project, and having many files concurrently
rotate-and-mint clients is wasteful and racy.

Now the global setup provisions exactly two shared clients once (one
provider-profile, one patient-profile), rotates + exchanges tokens a
single time, hands the tokens/profiles to every test via vitest inject,
and deletes both clients + their profiles in teardown. setupIntegrationTest
just consumes the injected tokens; its return shape is unchanged so all
existing tests keep working.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
create/update/delete-em-code all patch the single shared em-codes
ValueSet; running them as separate files risked optimistic-lock
conflicts under parallelism. Consolidated into one sequential
create -> update -> delete lifecycle (one happy-path assertion per
zambda), matching the insurance-override-list pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ore)

The support-dialog config is a single project-wide Basic resource. The
test captures the original in beforeAll and restores it in afterAll so
the shared project's config is left unchanged. The update returns 204
(SDK resolves null), so the effect is asserted by reading it back via
get-support-dialog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With one shared Practitioner/Patient profile per role, a test that
mutates the profile resource would corrupt every parallel test. Document
that testUserM2MProfile is a read-only caller identity, and harden
admin-delete-practitioner-role to attach its PractitionerRole to a
dedicated throwaway Practitioner instead of the shared profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ogress-note-config test

Several admin-* zambdas gate on requireUserWithRole (Administrator /
Manager / Customer Support). The shared provider M2M client now carries
all staff roles the project defines (Provider, Administrator, Manager,
Customer Support, Front Desk, Staff, Billing) so it can exercise those
endpoints — it stands in for a fully-privileged staff user.

Adds admin-update-progress-note-config (read-modify-restore: capture the
config, update with a marker, assert via get, restore original).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t re-save)

Reads the current label-printing config and writes the same value back
(the config is a project singleton on a Device), then asserts it
round-trips via get-label-printing-config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
admin-update-location-support-phones writes a support phone onto a
throwaway Location's extension (no shared state). save-invoice-config
writes the project's singleton invoicing QuestionnaireResponse — captured
and restored at the resource level (read-modify-restore).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-save)

Reads the current scheduled-outreach config and saves the same actions
back (re-saving identical actions reconciles to no task changes), then
asserts the action set round-trips. Completes the singleton-config
writers (support-dialog, progress-note, label-printing, location-phones,
invoice-config, scheduled-outreach all covered via read-modify-restore /
idempotent re-save).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get-patient-account returns the account graph for a seeded patient.
create-schedule builds a FHIR Schedule from DEFAULT_SCHEDULE_JSON owned
by a throwaway Location; both created resources are cleaned up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
create-manual-task creates a manual follow-up Task at a throwaway
Location. apply-template creates a note template from a seeded encounter
and applies it back to that encounter. Created resources are cleaned up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Toggles `active` on a throwaway insurance Organization and verifies the
change, then removes the Organization.

(Note: radiology-create-order is intentionally NOT covered — it makes an
unconditional outbound call to AdvaPACS, a third-party service, so it
cannot be exercised under the no-third-party-calls rule.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 'unsolicited-results-icon' request type is a no-setup FHIR read of
pending unsolicited lab results.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vendor

The radiology-create-order zambda POSTs unconditionally to AdvaPACS. Those
handlers run in the global-setup process (the in-process express server),
so global-setup now patches global fetch to intercept any AdvaPACS host
and return a 200 transaction-response Bundle; all other (Oystehr) traffic
passes through untouched. This lets radiology.test.ts exercise the real
create-order happy path (order persists, no rollback) without hitting the
third-party service. Fetch is restored in teardown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ary tests

With AdvaPACS mocked in global-setup (now method/URL-aware: a
ServiceRequest search resolves to one entry, a DiagnosticReport POST
returns a preliminary report), these radiology workflow endpoints run
their real happy paths against FHIR without hitting the vendor. Each
creates a radiology order in setup and cleans up the
ServiceRequest/Procedure/Task/DiagnosticReport afterwards.

(save-final-report, launch-viewer and pacs-webhook deferred: final-report
hits a masked error in its catch path; launch-viewer requires a completed
order + viewer-URL response; pacs-webhook is a complex inbound webhook.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…reaking userMe

save-final-report's validateSecrets returned a narrowed secrets object
(AUTH0_*, FHIR_API, PROJECT_API) that omitted ENVIRONMENT. The handler
then calls userMe(), which reads getSecret(ENVIRONMENT, secrets) — and
getOptionalSecret only falls back to process.env when secrets is null,
so a non-null narrowed object without ENVIRONMENT makes that call throw
("Secret or Environment Variable with key ENVIRONMENT was not set"). The
zambda's catch masked it as a generic 500. Carry ENVIRONMENT through.

Surfaced by adding the radiology-save-final-report integration test
(create order -> save preliminary report -> save final report; asserts
the DiagnosticReport flips to 'final'). AdvaPACS is mocked in global-setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Creates a radiology order, marks its ServiceRequest 'completed' (launch-
viewer's precondition), and asserts a viewer url is returned. Extends the
AdvaPACS mock so the /viewer/launch endpoint returns { url }.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Posts a ServiceRequest webhook (as AdvaPACS would) carrying our order's
accession number, authenticated with Bearer ADVAPACS_WEBHOOK_SECRET via a
direct POST to the public endpoint, and asserts the matching order's
status is updated. Exposes ADVAPACS_WEBHOOK_SECRET from the test secrets.

Completes radiology coverage (all 8 endpoints: order-list, create-order,
cancel-order, send-for-final-read, save-preliminary-report,
save-final-report, launch-viewer, pacs-webhook).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
alexwillingham and others added 28 commits June 13, 2026 18:58
submit-paperwork now fills the QR the way intake does: start from empty page
skeletons, save each page via the real patch-paperwork endpoint (serially),
then submit and assert the QR finalized (completed/amended). To keep this valid
for any instance and free of third-party calls, the answer synthesizer now picks
the answer option that triggers the fewest downstream required fields, which
selects a self-pay payment option over insurance and avoids the eligibility/3p
path; reference-backed required choices now use a syntactically valid uuid so
the FHIR save accepts them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get-appointment-details, get-slot-details, get-wait-status. All three need a
scheduling graph the base seed doesn't provide: get-appointment-details resolves
the schedule owner from the appointment's Location actor (create a Schedule with
a valid extension on the seed Location via testScheduleUtils); get-slot-details
needs a real Slot referencing that Schedule; get-wait-status requires the
encounter to carry a telemed video-room virtual-service extension (patched onto
the seed encounter via getTelemedRequiredAppointmentEncounterExtensions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eads

get-vitals (current vitals for the seed encounter), get-patient-visit-history
(seed patient), create-slot + walkin-check-availability + get-schedule (all use
the testScheduleUtils Schedule fixture on the seed Location; get-schedule also
creates a slug-identified Location so the owner resolves). FHIR-only, local env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both operate on a throwaway Location + Schedule (testScheduleUtils) so no shared
state is touched: ehr-get-schedule reads the schedule by id; update-schedule
updates the timezone. http_auth, provider M2M, local env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
icd-10-search hits the bundled ICD-10 dataset (local file, no 3p). get-patient-
coverages searches Coverage in the billing workspace for the seed patient (empty
list is a valid 200). FHIR-only/local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cancels the seed booked appointment with silent:true so the FHIR cancellation is
exercised without any email/SMS (3p). Patches a statusHistory onto the seed
encounter (cancel-appointment reads it, same gotcha as check-in).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-photo-url

get-chart-data reads the seed encounter's chart (empty but well-formed).
get-patient-profile-photo-url 'upload' action returns the z3 URL + a presigned
upload URL (Oystehr Z3 presigning only, no 3p).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…te-encounters-report

Both are read-only: get-label-printing-config returns the project's label config
(no deviceId); incomplete-encounters-report runs a FHIR encounter search over a
date range. No third-party calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/delete-chart-data

change-in-person-visit-status advances the seed visit status in place (patches an
open statusHistory first). save-chart-data saves a chief complaint then deletes it
via delete-chart-data so the created Condition doesn't leak — covers both. FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ncounter

generate-label-xml builds the visit label XML from the encounter + printing config.
save-followup-encounter runs in update mode against the seed encounter (avoids
leaking a new encounter); the seed encounter is given a /type first so the handler's
replace-/type patch applies. FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… cleanup)

Attaches a photo-id DocumentReference (z3 URL, no upload) to the seed patient,
diffs the patient's DocumentReferences to find the created one, then deletes it
via delete-visit-files — covers both endpoints and avoids leaking the DocRef.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Creates a service category, then deletes it by id via admin-delete-service-category
in teardown so it doesn't linger. Mirrors the existing delete test's pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tails

Both require an intake QuestionnaireResponse linked to the visit, so the seed QR is
pointed at the instance's active intake questionnaire (resolved from config via
getCurrentQuestionnaireForServiceType — instance-agnostic). get-visit-details is the
patient zambda; ehr-get-visit-details is the EHR one. FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Creates an in-house lab test definition (ActivityDefinition tagged latest +
Provenance) from a minimal AdminInHouseLabItemDefinition; deletes both in
teardown. FHIR-only. Foundation for the in-house lab order chain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ouse-lab-order)

Creates an in-house lab test definition (admin-add-in-house-lab), orders it for the
seed encounter, then deletes the order via delete-in-house-lab-order — covers both
order endpoints and cleans up. Encounter needs an Attender participant (same gotcha
as create-nursing-order). FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each creates a throwaway in-house lab definition then operates on it:
admin-get-in-house-lab-config reads it by activityDefinitionId; admin-update-in-house-lab
toggle-status flips its status in place (payload is {userId, data:{updateType,data:{activityDefinitionId}}}).
Both clean up the AD + provenances. FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sets up a lab definition + order, then marks the specimen collected (collectedBy.id
must match the encounter's attending practitioner). Tears down order + definition.
FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full in-house lab chain: define lab -> order -> collect specimen -> enter results.
Result data is keyed by the AD's contained ObservationDefinition ids (string value
for a Free Text component). Creates Observation + DiagnosticReport; tears the order
+ definition down. FHIR-only (the result-PDF Labs-List lookup is best-effort/swallowed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sets up a Medication + in-progress immunization order, then administers it
(type 'administered' requires full administrationDetails: mvx/cvx/ndc/lot/expDate/
administeredDateTime/visGivenDate). Removes order + Medication afterward. FHIR-only.
Completes the immunization endpoints (nursing-order get/create/update already covered).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…in-update-service-category

approved-patient-education: save an entry (PDF to z3) -> update its ICD codes ->
delete it (covers save/update-codes/delete-approved-patient-education; unique fake
ICD code so it never replaces a real instance entry). admin-update-service-category:
create -> rename -> delete. FHIR + Oystehr z3 only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ove-patient-coverage

create-update-medication-order: order a medication for the seed encounter
(MedicationRequest; route must be a valid SNOMED code, e.g. 26643006 Oral route);
finds + deletes the created order. remove-patient-coverage: seed a Coverage linked
into the patient's billing Account (priority 1, which is how the handler resolves
it), then remove it. FHIR-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cation

The mock returned a fixed DiagnosticReport id, so concurrent radiology tests all
stamped their local DR with the same AdvaPACS-resource identifier — and the
pacs-webhook handler matches on that identifier, so its search crossed tests and
the DR-finalize test flaked under full-suite parallelism. Mock now returns a
unique id per call; the webhook test reads its own DR's identifier back and posts
the webhook with that exact id. Radiology family verified green 3x in parallel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ad-after-write flakiness

Under full-suite parallelism (200+ files against one shared project), create-appointment's
pre-booking validation search (Slot?_id=X&_revinclude=Appointment:slot) occasionally
returns a phantom Appointment for a just-created unique slot id, throwing
APPOINTMENT_ALREADY_EXISTS_ERROR. The failure is in the validation phase BEFORE any
write (no half-booked state), passes in isolation, and clears on a re-issued search —
a search-index consistency artifact, not a logic bug. retry:2 absorbs it; two back-to-back
full runs went 207/207 green. (The radiology DR flake was a real identifier collision and
was fixed at the source, not retried.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ckend write pressure

Lowers how often the FHIR search read-after-write consistency artifact occurs by
keeping concurrent write load on the shared project modest, on top of retry:2.
Full suite stays green (207/207); wall time ~203s at 4 workers vs ~90s uncapped
(shared backend is the bottleneck) and ~10min fully serial.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6 workers keeps the full suite green (207/207) at ~143s wall vs ~203s at 4 and
~90s uncapped — a better speed/reliability balance with retry:2 backstopping the
rare search-consistency flake.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
update-invoice-task: seed a sendInvoiceToPatient Task (status 'ready') and update it
(kept at 'ready' so no send subscription fires); delete it after. export-invoices:
kick off a CSV export (creates an export Task, returns taskId), then check that
task's status — covers both modes; deletes the Task. FHIR + Oystehr z3 only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rwork-to-pdf

get-or-create-visit-label-pdf: generate the visit label PDF for the seed encounter
(z3 + DocumentReference), returns presigned URL. paperwork-to-pdf: render the seed
QR (pointed at the instance intake questionnaire) to a PDF. Both clean up created
DocumentReferences. FHIR + Oystehr z3 only (visit-details-to-pdf skipped — Stripe).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…c8/NODE_V8_COVERAGE)

vitest's v8 provider only instruments test workers, but zambda handlers run in the
globalSetup/main process (the in-process express server) — so it reported a bogus
0% line coverage. Wrapping the run in c8 sets NODE_V8_COVERAGE for the whole process
tree, capturing the main process's execution, and remaps via vite source maps to
real src/**.ts lines. Full integration suite: 44.52% lines (53866/120985), lcov+html
in coverage/integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AUTH0_AUDIENCE,
FHIR_API,
PROJECT_API,
// userMe() (and other downstream helpers) read ENVIRONMENT via getSecret;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extraneous comment

AUTH0_SECRET_TESTS: envConfig.AUTH0_SECRET_TESTS,
PROJECT_API: envConfig.PROJECT_API,
PROJECT_ID: envConfig.PROJECT_ID,
// Webhook secret AdvaPACS would sign its callbacks with; used by the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused about this file. Where are all the other secrets specified for local usage? Why do we need to add just the advapacs secret here, when so many other secrets used by other zambdas are not listed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant