Skip to main content
Glama
order-create.test.ts16.2 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { createReference, indexSearchParameterBundle, indexStructureDefinitionBundle } from '@medplum/core'; import type { MedplumClient } from '@medplum/core'; import { SEARCH_PARAMETER_BUNDLE_FILES, readJson } from '@medplum/definitions'; import type { Bundle, BundleEntry, Coverage, Organization, Patient, Practitioner, Questionnaire, QuestionnaireItem, QuestionnaireResponse, SearchParameter, ServiceRequest, } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import type { MockedFunction } from 'vitest'; import { buildVitalOrder, createVitalOrder, createVitalUser, handler, resourceWithoutMeta } from './order-create'; global.fetch = vi.fn(); describe('Create Order Bot', () => { 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>); } }); type Context = { medplum: MedplumClient; patient: Patient; requestingPhysician: Practitioner; coverage: Coverage; performer: Organization; questionnarie: Questionnaire; questionnarieResponse: QuestionnaireResponse; order: ServiceRequest; }; beforeEach<Context>(async (ctx) => { (global.fetch as MockedFunction<typeof fetch>).mockReset(); const medplum = new MockClient(); const patient = await medplum.createResource(buildPatient()); const requestingPhysician = await medplum.createResource(buildRequestingPhysician()); const performer = await medplum.createResource(buildPerformer()); const questionnarie = await medplum.createResource(buildQuestionnaire()); const questionnarieResponse = await medplum.createResource(buildQuestionnaireResponse(questionnarie)); const coverage = await medplum.createResource(buildCoverage(patient, performer)); const order = await medplum.createResource( buildServiceRequest(patient, requestingPhysician, performer, coverage, questionnarieResponse) ); Object.assign(ctx, { medplum, patient, requestingPhysician, coverage, performer, questionnarie, questionnarieResponse, order, }); }); afterEach(() => { vi.clearAllMocks(); vi.resetAllMocks(); }); test<Context>('buildVitalOrder', async (ctx) => { const bundle = await buildVitalOrder(ctx.medplum, ctx.order); expect(bundle.resourceType).toBe('Bundle'); expect(bundle.type).toBe('transaction'); expect(bundle.entry?.length).toBe(6); // Patient const patient = bundle.entry?.find((e: any) => e.resource.resourceType === 'Patient') as | BundleEntry<Patient> | undefined; expect(patient?.resource?.name).toStrictEqual(ctx.patient.name); expect(patient?.resource?.address?.[0].country).toStrictEqual('US'); // Questionnaire const questionnaireResponse = bundle.entry?.find( (e: any) => e.resource.resourceType === 'QuestionnaireResponse' ) as BundleEntry<QuestionnaireResponse> | undefined; expect(questionnaireResponse?.resource?.status).toBe('completed'); // Practitioner const practitioner = bundle.entry?.find((e: any) => e.resource.resourceType === 'Practitioner') as | BundleEntry<Practitioner> | undefined; expect(practitioner?.resource).toStrictEqual(resourceWithoutMeta(ctx.requestingPhysician)); const performer = bundle.entry?.find((e: any) => e.resource.resourceType === 'Organization') as | BundleEntry<Organization> | undefined; expect(performer?.resource).toStrictEqual(resourceWithoutMeta(ctx.performer)); // ServiceRequest const serviceRequest = bundle.entry?.find((e: any) => e.resource.resourceType === 'ServiceRequest') as | BundleEntry<ServiceRequest> | undefined; expect(serviceRequest?.resource).toStrictEqual(resourceWithoutMeta(ctx.order)); // Coverage const coverage = bundle.entry?.find((e: any) => e.resource.resourceType === 'Coverage') as | BundleEntry<Coverage> | undefined; expect(coverage?.resource).toStrictEqual(resourceWithoutMeta(ctx.coverage)); }); test<Context>('createOrder', async (ctx) => { const orderID = '3fa85f64-5717-4562-b3fc-2c963f66afa6'; (fetch as any).mockResolvedValue(createFetchResponse({ order: { id: orderID } }, 200)); const apiKey = '3f2504e0-4f89-11d3-9a0c-0305e82c3301'; const baseURL = 'https://api.dev.tryvital.io'; const bundle = await buildVitalOrder(ctx.medplum, ctx.order); const secrets = { VITAL_BASE_URL: { name: 'VITAL_BASE_URL', valueString: baseURL, }, VITAL_API_KEY: { name: 'VITAL_API_KEY', valueString: apiKey, }, }; const gotOrderID = await createVitalOrder(secrets, bundle); expect(gotOrderID).toBe(orderID); // Check that the order was sent to the Vital API expect(fetch).toHaveBeenCalledWith(`${baseURL}/v3/order/fhir`, { method: 'POST', body: JSON.stringify(bundle), headers: { 'Content-Type': 'application/fhir+json', 'x-vital-api-key': apiKey, }, }); }); test<Context>('createPatient', async (ctx) => { const userID = '3fa85f64-5717-4562-b3fc-2c963f66afa6'; const apiKey = '3f2504e0-4f89-11d3-9a0c-0305e82c3301'; const baseURL = 'https://api.dev.tryvital.io'; (fetch as any).mockResolvedValue(createFetchResponse({ client_user_id: ctx.patient.id, user_id: userID }, 200)); const secrets = { VITAL_BASE_URL: { name: 'VITAL_BASE_URL', valueString: baseURL, }, VITAL_API_KEY: { name: 'VITAL_API_KEY', valueString: apiKey, }, }; const goUserID = await createVitalUser(secrets, ctx.patient); expect(goUserID).toBe(userID); // Check that the patient was sent to the Vital API expect(fetch).toHaveBeenCalledWith(`${baseURL}/v2/user`, { method: 'POST', body: JSON.stringify({ client_user_id: ctx.patient.id }), headers: { 'Content-Type': 'application/json', 'x-vital-api-key': apiKey, }, }); }); test<Context>('createPatientAlreadyExists', async (ctx) => { const userID = '3fa85f64-5717-4562-b3fc-2c963f66afa6'; const apiKey = '3f2504e0-4f89-11d3-9a0c-0305e82c3301'; const baseURL = 'https://api.dev.tryvital.io'; (fetch as any).mockResolvedValue( createFetchResponse({ detail: { client_user_id: ctx.patient.id, user_id: userID } }, 400) ); const secrets = { VITAL_BASE_URL: { name: 'VITAL_BASE_URL', valueString: baseURL, }, VITAL_API_KEY: { name: 'VITAL_API_KEY', valueString: apiKey, }, }; const goUserID = await createVitalUser(secrets, ctx.patient); expect(goUserID).toBe(userID); // Check that the patient was sent to the Vital API expect(fetch).toHaveBeenCalledWith(`${baseURL}/v2/user`, { method: 'POST', body: JSON.stringify({ client_user_id: ctx.patient.id }), headers: { 'Content-Type': 'application/json', 'x-vital-api-key': apiKey, }, }); }); test<Context>('handler', async (ctx) => { const userID = '3fa85f64-5717-4562-b3fc-2c963f66afa6'; const orderID = '2a85f64-5717-4562-b3fc-2c963f66afa6'; const apiKey = '3f2504e0-4f89-11d3-9a0c-0305e82c3301'; const baseURL = 'https://api.dev.tryvital.io'; (fetch as MockedFunction<typeof fetch>) // Resolve the first fetch call with the patient ID .mockResolvedValueOnce(createFetchResponse({ client_user_id: ctx.patient.id, user_id: userID }, 200)) // Resolve the second fetch call with the order ID .mockResolvedValueOnce(createFetchResponse({ order: { id: orderID } }, 200)); await handler(ctx.medplum, { bot: { reference: 'Bot/123' }, input: ctx.order, contentType: 'application/fhir+json', secrets: { VITAL_BASE_URL: { name: 'VITAL_BASE_URL', valueString: baseURL, }, VITAL_API_KEY: { name: 'VITAL_API_KEY', valueString: apiKey, }, }, }); // Check that the patient was sent to the Vital API expect(fetch).toHaveBeenCalledWith(`${baseURL}/v2/user`, { method: 'POST', body: JSON.stringify({ client_user_id: ctx.patient.id }), headers: { 'Content-Type': 'application/json', 'x-vital-api-key': apiKey, }, }); // Check that the order was sent to the Vital API expect(fetch).toHaveBeenCalledWith(`${baseURL}/v3/order/fhir`, { method: 'POST', body: JSON.stringify(await buildVitalOrder(ctx.medplum, ctx.order)), headers: { 'Content-Type': 'application/fhir+json', 'x-vital-api-key': apiKey, }, }); }); }); const markers: Marker[] = [ { id: 374, name: 'Aluminum, Urine', slug: 'aluminum-urine', description: 'Aluminum, Urine', lab_id: 27, provider_id: '071555', type: 'biomarker', unit: null, price: 'N/A', aoe: { questions: [ { id: 1234567890212, required: true, code: 'COLVOL', value: 'URINE VOLUME (MILLILITERS)', type: 'numeric', sequence: 1, answers: [], }, ], }, expected_results: [], }, { id: 172, name: 'Triglycerides', slug: 'triglycerides', description: 'Triglycerides', lab_id: 27, provider_id: '001172', type: null, unit: null, price: 'N/A', aoe: { questions: [ { id: 1234567890364, required: false, code: 'FSTING', value: 'FASTING', type: 'text', sequence: 1, answers: [], }, ], }, expected_results: [], }, ] as const; type Marker = { id: number; name: string; slug: string; description: string; lab_id: number; provider_id: string; type: string | null; unit: string | null; price: string; aoe: { questions: { id: number; required: boolean; code: string; value: string; type: 'numeric' | 'text' | 'choice' | 'multiple_choice'; sequence: number; answers?: { id: number; code: string; value: string; }[]; }[]; }; expected_results: { id: number; name: string; slug: string; lab_id: number; required: boolean; provider_id: string; loinc: { id: number; name: string; slug: string; code: string; unit?: string; }; }[]; }; function buildQuestionnaire(): Questionnaire { return { resourceType: 'Questionnaire', title: 'Medicare Aoe', status: 'active', item: markers.map((marker) => ({ linkId: marker.id.toString(), text: marker.name, type: 'group', item: marker.aoe.questions.map<QuestionnaireItem>((question) => ({ linkId: question.id.toString(), text: question.value, type: (question.type === 'numeric' ? 'decimal' : question.type) as QuestionnaireItem['type'], required: question.required, })), })), }; } function buildQuestionnaireResponse(questionnaire: Questionnaire): QuestionnaireResponse { return { resourceType: 'QuestionnaireResponse', status: 'completed', questionnaire: questionnaire.id, item: [ { linkId: markers[0].id.toString(), text: markers[0].name, item: [ { linkId: markers[0].aoe.questions[0].id.toString(), answer: [ { valueDecimal: 123, }, ], }, ], }, { linkId: markers[1].id.toString(), text: markers[1].name, item: [ { linkId: markers[1].aoe.questions[0].id.toString(), answer: [ { valueString: 'Yes', }, ], }, ], }, ], }; } function createFetchResponse(data: any, status = 200): Response { return { status, json: () => new Promise((resolve) => { resolve(data); }), } as Response; } function buildPerformer(): Organization { return { resourceType: 'Organization', identifier: [ { system: 'https://docs.tryvital.io/api-reference/lab-testing/tests', value: '3fa85f64-5717-4562-b3fc-2c963f66afa6', }, ], type: [ { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/organization-type', code: 'prov', }, ], }, ], name: 'Acme Clinical Labs', }; } function buildRequestingPhysician(): Practitioner { return { resourceType: 'Practitioner', identifier: [ { system: 'http://hl7.org/fhir/sid/us-npi', value: '1234567890', }, ], name: [ { prefix: ['Dr.'], given: ['Pierre'], family: 'Gasly', }, ], telecom: [ { system: 'phone', value: '+17411709894', }, { system: 'email', value: 'test@test.com', }, ], address: [ { use: 'work', state: 'NY', }, { use: 'work', state: 'CA', }, ], gender: 'male', }; } function buildPatient(): Patient { return { resourceType: 'Patient', name: [ { given: ['Zinedine'], family: 'Zidane', }, ], birthDate: '1993-01-01', address: [ { line: ['West Lincoln Street'], city: 'Phoenix', state: 'AZ', postalCode: '85004', }, ], telecom: [ { system: 'phone', value: '+17411709894', }, { system: 'email', value: 'test@test.com', }, ], gender: 'male', }; } function buildCoverage(patient: Patient, performer: Organization): Coverage { return { resourceType: 'Coverage', network: 'Medicare', subscriber: createReference(patient), subscriberId: '1234567890', status: 'active', beneficiary: createReference(patient), payor: [createReference(performer)], relationship: { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/subscriber-relationship', code: 'self', }, ], }, }; } function buildServiceRequest( patient: Patient, requestingPhysician: Practitioner, performer: Organization, coverage: Coverage, questionnarieResponse: QuestionnaireResponse ): ServiceRequest { return { resourceType: 'ServiceRequest', subject: createReference(patient), requester: createReference(requestingPhysician), performer: [createReference(performer)], insurance: [createReference(coverage)], status: 'active', intent: 'order', code: { // Diagnosis codes (ICD-10) coding: [ { system: 'http://hl7.org/fhir/sid/icd-10-cm', code: 'I10.9', }, { system: 'http://hl7.org/fhir/sid/icd-10-cm', code: 'R50.9', }, ], }, supportingInfo: [createReference(questionnarieResponse)], note: [ // Subjective/symptoms { text: 'The patient reports experiencing headaches for the past week. The headaches are described as throbbing and are worse in the morning. The patient has also been experiencing nausea and vomiting.', }, ], // NOTE: This won't be in the out-of-the-box Medplum UI extension: [ { url: 'assessment_plan', valueString: 'Based on the symptoms, a CT scan of the head is recommended to rule out any underlying neurological causes.', }, ], }; }

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