Skip to main content
Glama
default-values.test.ts15.1 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { readJson } from '@medplum/definitions'; import type { Bundle, MedicationRequest, Observation, Patient, StructureDefinition } from '@medplum/fhirtypes'; import { HTTP_HL7_ORG } from './constants'; import { applyDefaultValuesToElement, applyDefaultValuesToResource, applyFixedOrPatternValue, getDefaultValuesForNewSliceEntry, } from './default-values'; import type { InternalSchemaElement, InternalTypeSchema, SliceDefinition, SlicingRules } from './typeschema/types'; import { indexStructureDefinitionBundle, tryGetProfile } from './typeschema/types'; import { isPopulated } from './utils'; function isStructureDefinition(sd: any): sd is StructureDefinition { if (!isPopulated<StructureDefinition>(sd)) { return false; } return sd.resourceType === 'StructureDefinition'; } function getSlicedElement( schema: InternalTypeSchema, slicedElementKey: string ): InternalSchemaElement & { slicing: SlicingRules } { const slicedElement = schema.elements[slicedElementKey]; if (!isPopulated(slicedElement)) { fail(`Expected ${slicedElementKey} element to be defined`); } if (!isPopulated(slicedElement.slicing)) { fail(`Expected slicing to exist on ${slicedElementKey} element`); } return slicedElement as InternalSchemaElement & { slicing: SlicingRules }; } function getSlice(schema: InternalTypeSchema, slicedElementKey: string, sliceName: string): SliceDefinition { const slicedElement = getSlicedElement(schema, slicedElementKey); const slice = slicedElement.slicing?.slices.find((s) => s.name === sliceName); if (!isPopulated(slice)) { fail(`Expected ${sliceName} slice to be defined`); } return slice; } describe('apply default values', () => { let USCoreStructureDefinitions: StructureDefinition[]; beforeAll(() => { indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); }); function loadProfiles(profileUrls: string[]): void { const sds: StructureDefinition[] = profileUrls .map((profileUrl) => { return USCoreStructureDefinitions.find((sd) => sd.url === profileUrl); }) .filter(isStructureDefinition); expect(sds.length).toStrictEqual(profileUrls.length); indexStructureDefinitionBundle(sds); } describe('US Blood Pressure', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-blood-pressure`; const profileUrls = [profileUrl]; let schema: InternalTypeSchema; beforeAll(() => { loadProfiles(profileUrls); schema = tryGetProfile(profileUrl) as InternalTypeSchema; if (!schema) { fail(`Failed to load schema for ${profileUrl}`); } }); test('new Blood Pressure observation', async () => { // casting to avoid specifying any required (according to typescript) fields // since populating them is the point of the code being tested const resource: Observation = { resourceType: 'Observation' } as Observation; const withDefaults = applyDefaultValuesToResource(resource, schema); // fixed values in Observation.component.value[x] excluded since value[x] itself is optional (min === 0) // In other words, { valueQuantity: {code: "mm[Hg]", system: "http://unitsofmeasure.org"} } should NOT be included // in either component expect(withDefaults).toEqual({ resourceType: 'Observation', category: [ { coding: [ { code: 'vital-signs', system: 'http://terminology.hl7.org/CodeSystem/observation-category', }, ], }, ], code: { coding: [ { system: 'http://loinc.org', code: '85354-9', }, ], }, component: [ { code: { coding: [ { system: 'http://loinc.org', code: '8480-6', }, ], }, }, { code: { coding: [ { system: 'http://loinc.org', code: '8462-4', }, ], }, }, ], }); }); describe('required values within optional element', () => { test('value for Observation.component.value[x] in systolic slice', () => { const slice = getSlice(schema, 'component', 'systolic'); expect(slice.elements['value[x]'].min).toStrictEqual(0); const result = applyDefaultValuesToElement(Object.create(null), slice.elements, 'value[x]'); expect(result).toEqual({ code: 'mm[Hg]', system: 'http://unitsofmeasure.org' }); }); }); }); describe('US Core Patient', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; const raceExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; const ethnicityExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`; const profileUrls = [ profileUrl, raceExtensionUrl, ethnicityExtensionUrl, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, ]; let schema: InternalTypeSchema; beforeAll(() => { loadProfiles(profileUrls); schema = tryGetProfile(profileUrl) as InternalTypeSchema; if (!schema) { fail(`Failed to load schema for ${profileUrl}`); } }); test('new Patient has no fixed/pattern values', async () => { const resource: Patient = { resourceType: 'Patient' }; const withDefaults = applyDefaultValuesToResource(resource, schema); expect(withDefaults).toEqual({ resourceType: 'Patient' }); // For now, a different object is returned by design expect(withDefaults).not.toBe(resource); }); test('HomerSimpsonUSCorePatient', async () => { const resource = getComplexUSCorePatient(); const withDefaults = applyDefaultValuesToResource(resource, schema); const expected = getComplexUSCorePatient(); // Prepare expected value // Expect stub values for a slice named 'text' to have been added for race and ethnicity extensions [ethnicityExtensionUrl, raceExtensionUrl].forEach((extUrl) => { const ext = expected.extension?.find((e) => e.url === extUrl); if (ext?.extension === undefined) { fail(`expected ${extUrl} extensions to exist`); } const textExt = ext.extension?.find((e) => e.url === 'text'); expect(textExt).toBeUndefined(); ext.extension.push({ url: 'text' }); }); expect(withDefaults).toEqual(expected); }); describe('fixed/pattern values within non-required extension slice entry', () => { test('new race extension entry', () => { const sliceSchema = tryGetProfile(raceExtensionUrl) as InternalTypeSchema; if (!sliceSchema) { fail(`Failed to load schema for ${raceExtensionUrl}`); } const result = applyDefaultValuesToElement(Object.create(null), sliceSchema.elements); expect(result).toEqual({ url: raceExtensionUrl }); }); test('new race extension entry with visitor', () => { const slicedElement = getSlicedElement(schema, 'extension'); const slice = getSlice(schema, 'extension', 'race'); const result = getDefaultValuesForNewSliceEntry('extension', slice, slicedElement.slicing, schema); expect(result).toEqual({ url: raceExtensionUrl }); }); }); test('applyFixedOrPatternValue with intermediate elements undefined', () => { const elem = schema.elements['identifier.value']; expect(elem).toBeDefined(); expect(elem.fixed).toBeUndefined(); // define a fake fixed value elem.fixed = { type: 'string', value: '42' }; const result = applyFixedOrPatternValue({}, 'identifier.value', elem, schema.elements); expect(result).toEqual({ identifier: [{ value: '42' }] }); delete elem.fixed; expect(elem.fixed).toBeUndefined(); }); test('applyFixedOrPatternValue with intermediate elements undefined', () => { const elem = schema.elements['identifier.value']; expect(elem).toBeDefined(); expect(elem.fixed).toBeUndefined(); // define a fake fixed value elem.fixed = { type: 'string', value: '42' }; const result = applyFixedOrPatternValue({}, 'identifier.value', elem, schema.elements); expect(result).toEqual({ identifier: [{ value: '42' }] }); delete elem.fixed; expect(elem.fixed).toBeUndefined(); }); test('applyFixedOrPatternValue on choice of types', () => { const key = 'multipleBirth[x]'; const originalElem = schema.elements[key]; expect(originalElem).toBeDefined(); schema.elements[key] = { ...originalElem, fixed: { type: 'integer', value: 2, }, type: [ { code: 'integer', targetProfile: undefined, profile: undefined, }, ], }; const elem = schema.elements[key]; const result = applyFixedOrPatternValue({}, key, elem, schema.elements); expect(result).toEqual({ multipleBirthInteger: 2 }); schema.elements[key] = originalElem; }); test('applyFixedOrPatternValue with non-empty array', () => { const key = 'maritalStatus'; const elem = schema.elements[key]; expect(elem).toBeDefined(); expect(elem.pattern).toBeUndefined(); // define a fake pattern value elem.pattern = { type: 'CodeableConcept', value: { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK', }, ], }, }; expect(applyFixedOrPatternValue({}, key, elem, schema.elements)).toEqual({ maritalStatus: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK' }] }, }); expect(applyFixedOrPatternValue({ maritalStatus: { coding: [] } }, key, elem, schema.elements)).toEqual({ maritalStatus: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK' }] }, }); expect(applyFixedOrPatternValue({ maritalStatus: { coding: [{}] } }, key, elem, schema.elements)).toEqual({ maritalStatus: { coding: [{}] }, }); // degenerate case; maritalStatus should NOT be an array expect(applyFixedOrPatternValue({ maritalStatus: [] }, key, elem, schema.elements)).toEqual({ maritalStatus: [], }); // unexpected pattern value type elem.pattern = { ...elem.pattern, value: 42 }; expect(applyFixedOrPatternValue({}, key, elem, schema.elements)).toEqual({}); delete elem.pattern; expect(elem.pattern).toBeUndefined(); }); }); describe('US Core Smoking Status', () => { const profileUrl = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus'; beforeAll(() => { const smokingStatusProfile: StructureDefinition = readJson('fhir/r4/testing/uscore-v7.0.0-smoking-status.json'); indexStructureDefinitionBundle([smokingStatusProfile]); }); test('Slice on singleton element does not error', () => { const schema = tryGetProfile(profileUrl); expect(schema).toBeDefined(); const resource: Observation = { resourceType: 'Observation', status: 'final', code: { coding: [{ system: 'http://loinc.org', code: '72166-2' }] }, }; const withDefaults = applyDefaultValuesToResource(resource, schema as InternalTypeSchema); expect(withDefaults).toEqual(resource); }); }); describe('US Core MedicationRequest', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`; let schema: InternalTypeSchema; beforeAll(() => { loadProfiles([profileUrl]); schema = tryGetProfile(profileUrl) as InternalTypeSchema; if (!schema) { fail(`Failed to load schema for ${profileUrl}`); } }); test('apply defaults to dosageInstruction handled correctly', () => { const resource: MedicationRequest = { resourceType: 'MedicationRequest', status: 'cancelled', intent: 'proposal', subject: { reference: 'Patient/123', }, dosageInstruction: [ { timing: { repeat: { period: 1, periodUnit: 'd', }, }, }, ], }; const withDefaults = applyDefaultValuesToResource(resource, schema); expect(withDefaults).toEqual(resource); }); }); }); function getComplexUSCorePatient(): Patient { return { resourceType: 'Patient', id: '123', gender: 'male', meta: { versionId: '2', lastUpdated: '2020-01-02T00:00:00.000Z', author: { reference: 'Practitioner/123', }, }, identifier: [ { system: 'abc', value: '123' }, { system: 'def', value: '456' }, ], active: true, birthDate: '1956-05-12', name: [ { given: ['Homer'], family: 'Simpson', }, ], extension: [ { extension: [ { valueCoding: { system: 'urn:oid:2.16.840.1.113883.6.238', code: '2106-3', display: 'White', }, url: 'ombCategory', }, ], url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, }, { extension: [ { valueCoding: { system: 'urn:oid:2.16.840.1.113883.6.238', code: '2186-5', display: 'Not Hispanic or Latino', }, url: 'ombCategory', }, ], url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, }, { valueCode: 'M', url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, }, { valueCode: 'M', url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-sex`, }, { valueCodeableConcept: { coding: [ { system: 'urn:oid:2.16.840.1.113762.1.4.1021.32', code: 'M', display: 'Male', }, ], }, url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, }, ], }; }

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