Skip to main content
Glama
useHealthGorillaLabOrder.test.tsx14.9 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { createReference, deepClone, getExtension, getReferenceString, indexSearchParameterBundle, indexStructureDefinitionBundle, } from '@medplum/core'; import { readJson, SEARCH_PARAMETER_BUNDLE_FILES } from '@medplum/definitions'; import type { Bundle, Coverage, Location, Organization, Patient, Practitioner, Reference, SearchParameter, } from '@medplum/fhirtypes'; import type { DiagnosisCodeableConcept, LabOrganization, TestCoding } from '@medplum/health-gorilla-core'; import { HEALTH_GORILLA_AUTHORIZED_BY_EXT, HEALTH_GORILLA_SYSTEM } from '@medplum/health-gorilla-core'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react'; import { act, renderHook } from '@testing-library/react'; import type { JSX } from 'react'; import { MemoryRouter } from 'react-router'; import { vi } from 'vitest'; import { getMockAutocompleteBot, QUERY_FOR_TEST_WITHOUT_AOE, REQUIRED_AOE_TEST, RWS_AOE_TEST, } from './autocomplete-endpoint.test'; import type { HealthGorillaLabOrderState, UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext'; import { HealthGorillaLabOrderProvider } from './HealthGorillaLabOrderProvider'; import { expectToBeDefined } from './test-utils'; import type { UseHealthGorillaLabOrderOptions } from './useHealthGorillaLabOrder'; import { useHealthGorillaLabOrder } from './useHealthGorillaLabOrder'; import { useHealthGorillaLabOrderContext } from './useHealthGorillaLabOrderContext'; const DIAGNOSES = [ { coding: [ { system: 'http://hl7.org/fhir/sid/icd-10-cm', code: 'D63.1', }, ], text: 'D63.1', } as DiagnosisCodeableConcept, { coding: [ { system: 'http://hl7.org/fhir/sid/icd-10-cm', code: 'E04.2', }, ], text: 'E04.2', } as DiagnosisCodeableConcept, ]; describe('useHealthGorilla', () => { let medplum: MockClient; let patient: Patient; let requester: Practitioner; beforeAll(() => { indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { indexSearchParameterBundle(readJson(filename) as Bundle<SearchParameter>); } }); beforeEach(async () => { medplum = new MockClient(); patient = await medplum.createResource({ resourceType: 'Patient', name: [{ text: 'Test Patient' }], }); requester = await medplum.createResource({ resourceType: 'Practitioner', name: [{ text: 'Test Practitioner' }], }); vi.spyOn(medplum, 'executeBot').mockImplementation(getMockAutocompleteBot({})); }); afterEach(() => { vi.resetAllMocks(); }); function setup({ patient, requester, requestingLocation, }: UseHealthGorillaLabOrderOptions): ReturnType<typeof renderHook<UseHealthGorillaLabOrderReturn, unknown>> { return renderHook(() => useHealthGorillaLabOrder({ patient, requester, requestingLocation }), { wrapper: ({ children }) => ( <MemoryRouter> <MedplumProvider medplum={medplum}>{children}</MedplumProvider> </MemoryRouter> ), }); } function setupContext( hookOptions: UseHealthGorillaLabOrderOptions, withoutProvider?: boolean ): ReturnType<typeof renderHook<UseHealthGorillaLabOrderReturn, unknown>> { function WrapperComponent({ children }: { children: React.ReactNode }): JSX.Element { const result = useHealthGorillaLabOrder(hookOptions); if (withoutProvider) { return <div> {children}</div>; } return <HealthGorillaLabOrderProvider {...result}>{children}</HealthGorillaLabOrderProvider>; } return renderHook(() => useHealthGorillaLabOrderContext(), { wrapper: ({ children }) => ( <MemoryRouter> <MedplumProvider medplum={medplum}> <WrapperComponent>{children}</WrapperComponent> </MedplumProvider> </MemoryRouter> ), }); } test('Context without provider throws', async () => { vi.spyOn(console, 'error').mockImplementation(() => {}); expect(() => setupContext({ patient, requester }, true)).toThrow(); }); test.each([ ['hook', setup], ['provider and context', setupContext], ])('Happy path to creating an order with %s', async (description, setupFunc) => { const { result } = setupFunc({ patient, requester }); expect(result.current.state).toEqual(getDefaultState()); expectToBeDefined(result.current.validateOrder()); const labs = await result.current.searchAvailableLabs('quest'); expect(labs).toHaveLength(1); const performingLab: LabOrganization = labs[0]; await act(async () => { result.current.setPerformingLab(labs[0]); }); expect(result.current.state).toEqual({ ...getDefaultState(), performingLab, }); expectToBeDefined(result.current.validateOrder()); const tests = await result.current.searchAvailableTests(QUERY_FOR_TEST_WITHOUT_AOE); expect(tests).toHaveLength(1); const test: TestCoding = tests[0]; await act(async () => { result.current.addTest(test); }); expect(result.current.state).toEqual({ ...getDefaultState(), performingLab, selectedTests: [test], testMetadata: { [test.code]: { aoeStatus: 'none' } }, }); // set bill to await act(async () => { result.current.updateBillingInformation({ billTo: 'patient' }); }); expect(result.current.state).toEqual({ ...getDefaultState(), performingLab, selectedTests: [test], testMetadata: { [test.code]: { aoeStatus: 'none' } }, billingInformation: { billTo: 'patient' }, }); // Is valid order now expect(result.current.validateOrder()).toBeUndefined(); const specimenCollectedDateTime = new Date(2024, 8, 9, 16, 30); await act(async () => { result.current.updateTestMetadata(test, { notes: 'Test 0 note', priority: 'urgent' }); result.current.setOrderNotes('This is a note about the whole order'); result.current.setSpecimenCollectedDateTime(specimenCollectedDateTime); result.current.updateBillingInformation({ billTo: 'customer-account' }); result.current.addDiagnosis(DIAGNOSES[0]); result.current.addDiagnosis(DIAGNOSES[1]); result.current.removeDiagnosis(DIAGNOSES[0]); }); expect(result.current.state).toEqual({ ...getDefaultState(), performingLab, selectedTests: [test], testMetadata: { [test.code]: { aoeStatus: 'none', priority: 'urgent', notes: 'Test 0 note' } }, orderNotes: 'This is a note about the whole order', billingInformation: { billTo: 'customer-account' }, diagnoses: [DIAGNOSES[1]], specimenCollectedDateTime, } as HealthGorillaLabOrderState); expect(result.current.validateOrder).not.toThrow(); const order = await result.current.createOrderBundle(); expect(order.serviceRequest.reasonCode).toEqual([DIAGNOSES[1]]); }); test('patient and requester as references', async () => { setup({ patient: createReference(patient) as Reference<Patient> & { reference: string }, requester: createReference(requester) as Reference<Practitioner> & { reference: string }, }); }); test.each([ [{ resourceType: 'Location', id: 'L-123' } satisfies Location, { reference: 'Location/L-123' }], [{ reference: 'Location/L-123' } satisfies Reference<Location>, { reference: 'Location/L-123' }], [{ resourceType: 'Organization', id: 'O-123' } satisfies Organization, { reference: 'Organization/O-123' }], [{ reference: 'Organization/O-123' } satisfies Reference<Organization>, { reference: 'Organization/O-123' }], ])( 'Requesting Location set', async ( requestingLocation: Location | Organization | (Reference<Location | Organization> & { reference: string }), expectedValue ) => { const { result } = setup({ patient, requester, requestingLocation }); // Make sure the order is valid await act(async () => { result.current.addTest(RWS_AOE_TEST); result.current.setPerformingLab({ resourceType: 'Organization', id: 'Lab-123' }); result.current.updateBillingInformation({ billTo: 'patient' }); }); const { serviceRequest } = await result.current.createOrderBundle(); expect(getExtension(serviceRequest, HEALTH_GORILLA_AUTHORIZED_BY_EXT)?.valueReference).toMatchObject( expectedValue ); } ); test('AOE required when specimen', async () => { const { result } = setup({ patient, requester }); await act(async () => { result.current.addTest(RWS_AOE_TEST); result.current.setSpecimenCollectedDateTime(new Date()); }); // AOE should be required since a specimentCollectedDateTime is set expectToBeDefined(result.current.validateOrder()?.testMetadata?.[RWS_AOE_TEST.code]?.aoeResponses); await act(async () => { result.current.setSpecimenCollectedDateTime(undefined); }); // AOE should now NOT be required since a specimentCollectedDateTime is removed expect(result.current.validateOrder()?.testMetadata?.[RWS_AOE_TEST.code]?.aoeResponses).toBeUndefined(); }); test('required AOE', async () => { const { result } = setup({ patient, requester }); await act(async () => { result.current.addTest(REQUIRED_AOE_TEST); }); expectToBeDefined(result.current.state.testMetadata[REQUIRED_AOE_TEST.code]?.aoeQuestionnaire); //TODO check that answers are required when submitting }); test('getActivePatientCoverages', async () => { const withoutInputs = setup({ patient: undefined, requester: undefined }); let coverage = await withoutInputs.result.current.getActivePatientCoverages(); expect(coverage).toHaveLength(0); const { result } = setup({ patient, requester }); coverage = await result.current.getActivePatientCoverages(); expect(coverage).toHaveLength(0); const count = 2; for (let i = 0; i < count; i++) { await medplum.createResource({ resourceType: 'Coverage', status: 'active', payor: [{ reference: getReferenceString(patient) }], beneficiary: { reference: getReferenceString(patient) }, } as Coverage); } coverage = await result.current.getActivePatientCoverages(); expect(coverage).toHaveLength(2); }); test('Kitchen sink', async () => { // A bunch of tests to try all the various permutations of the API const requestingLocation = { resourceType: 'Location', id: 'L-123' } satisfies Location; const expectedValue = { reference: 'Location/L-123' }; const { result } = setup({ patient, requester, requestingLocation }); expect(await result.current.searchAvailableTests('')).toEqual([]); expect(await result.current.searchAvailableLabs('')).toEqual([]); await act(async () => { result.current.addTest(RWS_AOE_TEST); result.current.removeTest(RWS_AOE_TEST); result.current.setTests([RWS_AOE_TEST]); result.current.setPerformingLab({ resourceType: 'Organization', id: 'Lab-123' }); result.current.setPerformingLabAccountNumber('123'); result.current.updateBillingInformation({ billTo: 'patient' }); result.current.setDiagnoses(DIAGNOSES); result.current.addDiagnosis(DIAGNOSES[0]); result.current.removeDiagnosis(DIAGNOSES[0]); result.current.removeDiagnosis({ coding: [{ system: 'http://hl7.org/fhir/sid/icd-10-cm', code: 'fake' }], } satisfies DiagnosisCodeableConcept); }); // The current performing lab does not have a Health Gorilla identifier, so this will fail await expect(async () => result.current.searchAvailableTests('')).rejects.toThrow( 'No Health Gorilla identifier found for performing lab' ); // Set a performing lab with a Health Gorilla identifier await act(async () => { result.current.setPerformingLab({ resourceType: 'Organization', id: 'Lab-123', identifier: [{ system: HEALTH_GORILLA_SYSTEM, value: '123' }], }); }); expect(await result.current.searchAvailableTests('')).toEqual([]); expect(await result.current.searchAvailableLabs('')).toEqual([]); const { serviceRequest } = await result.current.createOrderBundle(); expect(getExtension(serviceRequest, HEALTH_GORILLA_AUTHORIZED_BY_EXT)?.valueReference).toMatchObject(expectedValue); }); test('Error creating service request', async () => { const requestingLocation = { resourceType: 'Location', id: 'L-123' } satisfies Location; const { result } = setup({ patient, requester, requestingLocation }); await act(async () => { result.current.addTest(RWS_AOE_TEST); result.current.setPerformingLab({ resourceType: 'Organization', id: 'Lab-123' }); result.current.updateBillingInformation({ billTo: 'patient' }); }); // Mock the case of returning a Bundle with a non-2XX status code medplum.executeBatch = vi.fn( async () => ({ resourceType: 'Bundle', type: 'transaction-response', entry: [{ response: { status: '400' } }], }) as Bundle ); await expect(() => result.current.createOrderBundle()).rejects.toThrow( 'Error creating lab order: Non-2XX status code in response entry' ); }); test('Missing service request', async () => { const requestingLocation = { resourceType: 'Location', id: 'L-123' } satisfies Location; const { result } = setup({ patient, requester, requestingLocation }); await act(async () => { result.current.addTest(RWS_AOE_TEST); result.current.setPerformingLab({ resourceType: 'Organization', id: 'Lab-123' }); result.current.updateBillingInformation({ billTo: 'patient' }); }); // Mock the case of returning a Bundle without a ServiceRequest medplum.executeBatch = vi.fn( async () => ({ resourceType: 'Bundle', type: 'transaction-response', entry: [], }) as Bundle ); await expect(() => result.current.createOrderBundle()).rejects.toThrow( 'Error creating lab order: Lab Order Service Request not found in response entries' ); }); }); function getDefaultState(): HealthGorillaLabOrderState { // A bit of an abstraction violation here. Pass through deepClone to avoid considering missing `undefined` values return deepClone({ performingLab: undefined, performingLabAccountNumber: undefined, billingInformation: {}, selectedTests: [], testMetadata: {}, diagnoses: [], orderNotes: undefined, specimenCollectedDateTime: undefined, }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server