Skip to main content
Glama
elements-context.test.ts13.7 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { readJson } from '@medplum/definitions'; import type { AccessPolicy, Bundle, Patient, StructureDefinition } from '@medplum/fhirtypes'; import { AccessPolicyInteraction, satisfiedAccessPolicy } from './access'; import { HTTP_HL7_ORG } from './constants'; import type { ExtendedInternalSchemaElement } from './elements-context'; import { buildElementsContext } from './elements-context'; import type { InternalTypeSchema } from './typeschema/types'; import { getDataType, indexStructureDefinitionBundle, parseStructureDefinition } from './typeschema/types'; import { isPopulated } from './utils'; describe('buildElementsContext', () => { const DEFAULT_EXTENDED_PROPS = { readonly: false, hidden: false }; const HIDDEN = { readonly: true, hidden: true }; const READONLY = { readonly: true, hidden: false }; let USCoreStructureDefinitions: StructureDefinition[]; function getSchemaFromProfileUrl(url: string): InternalTypeSchema { const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); if (!isPopulated(sd)) { fail(`Expected structure definition for ${url} to be found`); } return parseStructureDefinition(sd); } beforeAll(() => { USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); }); test('deeply nested schema', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`; const schema = getSchemaFromProfileUrl(profileUrl); const context = buildElementsContext({ elements: schema.elements, path: 'MedicationRequest', parentContext: undefined, profileUrl, }); if (context === undefined) { fail('Expected context to be defined'); } expect(context.profileUrl).toStrictEqual(profileUrl); expect(context.elements['dosageInstruction.method']).toBeDefined(); expect(context.elementsByPath['MedicationRequest.dosageInstruction.method']).toBeDefined(); expect(context.elements['dosageInstruction.method']).toBe( context.elementsByPath['MedicationRequest.dosageInstruction.method'] ); }); test('building context at same path returns undefined', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; const schema = getSchemaFromProfileUrl(profileUrl); const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, profileUrl, }); if (context === undefined) { fail('Expected context to be defined'); } const samePath = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: context, profileUrl, }); expect(samePath).toBeUndefined(); }); test('nested context', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; const profileSchema = getSchemaFromProfileUrl(profileUrl); const context = buildElementsContext({ elements: profileSchema.elements, path: 'Patient', parentContext: undefined, profileUrl, }); if (context === undefined) { fail('Expected context to be defined'); } const extensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; const extensionSchema = getSchemaFromProfileUrl(extensionUrl); const extensionContext = buildElementsContext({ elements: extensionSchema.elements, path: 'Patient.extension', parentContext: context, profileUrl: extensionUrl, debugMode: true, }); if (extensionContext === undefined) { fail('Expected extension context to be defined'); } expect(extensionContext.profileUrl).toStrictEqual(extensionUrl); expect(Object.keys(extensionContext.elements)).toStrictEqual( expect.arrayContaining(['extension', 'id', 'url', 'value[x]']) ); expect(extensionContext.elements['extension'].slicing?.slices.length).toBe(3); expect(extensionContext.elements['extension']).toBe(extensionContext.elementsByPath['Patient.extension.extension']); expect(extensionContext.elements['url'].fixed).toStrictEqual({ type: 'uri', value: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race', }); }); test('no hidden fields', () => { const schema = getDataType('Patient'); const accessPolicy: AccessPolicy = { resourceType: 'AccessPolicy', resource: [{ resourceType: 'Patient', hiddenFields: [] }], }; const resource: Patient = { resourceType: 'Patient', }; const apr = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); expect(apr).toBeDefined(); const entriesBefore = Object.values(schema.elements).filter(Boolean).length; expect(entriesBefore).toStrictEqual(24); // sanity check const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, accessPolicyResource: apr, }); if (context === undefined) { fail('Expected context to be defined'); } const entriesAfter = Object.values(context.elements).filter(Boolean).length; expect(entriesBefore - entriesAfter).toStrictEqual(0); for (const key of Object.keys(context.elements)) { expect(context.getExtendedProps('Patient.' + key)).toStrictEqual(DEFAULT_EXTENDED_PROPS); } expect(context.getExtendedProps('Patient')).toBeUndefined(); expect(context.getExtendedProps('Patient.')).toBeUndefined(); expect(context.getExtendedProps('Patient.badKey')).toStrictEqual(DEFAULT_EXTENDED_PROPS); }); test('some hidden fields', () => { const schema = getDataType('Patient'); const accessPolicy: AccessPolicy = { resourceType: 'AccessPolicy', resource: [{ resourceType: 'Patient', hiddenFields: ['gender', 'multipleBirthInteger'] }], }; const resource: Patient = { resourceType: 'Patient', }; const apr = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); expect(schema.elements['gender']).toBeDefined(); const entriesBefore = Object.values(schema.elements).filter(Boolean).length; const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, accessPolicyResource: apr, }); if (context === undefined) { fail('Expected context to be defined'); } const entriesAfter = Object.values(context.elements).filter(Boolean).length; // "multipelBirthInteger" is one of the possible types for "multipleBirth[x]", but // InternalTypeSchema references elements by their path, i.e. "multipleBirth[x]", so // attempting to hide "multipleBirthInteger" is expected to have no effect expect(context.elements['gender']).toBeUndefined(); expect(entriesBefore - entriesAfter).toStrictEqual(1); expect(context.getExtendedProps('Patient.gender')).toStrictEqual(HIDDEN); }); test('hidden parent element also removes child elements', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; const schema = getSchemaFromProfileUrl(profileUrl); const accessPolicy: AccessPolicy = { resourceType: 'AccessPolicy', resource: [{ resourceType: 'Patient', hiddenFields: ['name'] }], }; const resource: Patient = { resourceType: 'Patient', }; const apr = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); const entriesBefore = Object.values(schema.elements).filter(Boolean).length; const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, accessPolicyResource: apr, }); if (context === undefined) { fail('Expected context to be defined'); } const entriesAfter = Object.values(context.elements).filter(Boolean).length; // includes name, name.id, name.use, name.family, name.given, etc. expect(entriesBefore - entriesAfter).toStrictEqual(10); expect(context.getExtendedProps('Patient.name')).toStrictEqual(HIDDEN); expect(context.getExtendedProps('Patient.name.given')).toStrictEqual(HIDDEN); expect(context.getExtendedProps('Patient.name.family')).toStrictEqual(HIDDEN); }); test('hidden nested field leaves parent and siblings', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; const schema = getSchemaFromProfileUrl(profileUrl); const accessPolicy: AccessPolicy = { resourceType: 'AccessPolicy', resource: [{ resourceType: 'Patient', hiddenFields: ['name.family'] }], }; const resource: Patient = { resourceType: 'Patient', }; const apr = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); expect(schema.elements['name.family']).toBeDefined(); const entriesBefore = Object.values(schema.elements).filter(Boolean).length; const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, accessPolicyResource: apr, }); if (context === undefined) { fail('Expected context to be defined'); } const entriesAfter = Object.values(context.elements).filter(Boolean).length; expect(context.elements['name.family']).toBeUndefined(); expect(entriesBefore - entriesAfter).toStrictEqual(1); }); test('readonly fields are marked as readonly', () => { const schema = getDataType('Patient'); const accessPolicy: AccessPolicy = { resourceType: 'AccessPolicy', resource: [ { resourceType: 'Patient', readonlyFields: ['gender', 'multipleBirthInteger', 'name.given', 'identifier'] }, ], }; const resource: Patient = { resourceType: 'Patient', }; const apr = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); expect(schema.elements['gender']).toBeDefined(); const entriesBefore = Object.values(schema.elements).filter(Boolean).length; const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, accessPolicyResource: apr, }); if (context === undefined) { fail('Expected context to be defined'); } const entriesAfter = Object.values(context.elements).filter(Boolean).length; expect(entriesBefore - entriesAfter).toStrictEqual(0); expect(context.elements['gender'].readonly).toBe(true); expect(context.getExtendedProps('Patient.gender')).toStrictEqual(READONLY); // "multipelBirthInteger" is one of the possible types for "multipleBirth[x]", but // InternalTypeSchema references elements by their path, i.e. "multipleBirth[x]", so // attempting to hide "multipleBirthInteger" is expected to have no effect expect(context.elements['multipleBirth[x]']).toBeDefined(); expect(context.elements['multipleBirth[x]'].readonly).toBeUndefined(); expect(context.getExtendedProps('Patient.multipleBirth[x]')).toStrictEqual(DEFAULT_EXTENDED_PROPS); // name.given isn't explicitly an element in the schema, but it should still be marked as readonly via getExtendedProps // parent and sibling elements are not marked as readonly expect(context.elements['name.given']).toBeUndefined(); expect(context.getExtendedProps('Patient.name.given')).toStrictEqual(READONLY); expect(context.getExtendedProps('Patient.name')).toStrictEqual(DEFAULT_EXTENDED_PROPS); expect(context.getExtendedProps('Patient.name.family')).toStrictEqual(DEFAULT_EXTENDED_PROPS); // nested elements are also marked as readonly expect(context.elements['identifier'].readonly).toBe(true); expect(context.elements['identifier.system']).toBeUndefined(); expect(context.getExtendedProps('Patient.identifier')).toStrictEqual(READONLY); expect(context.getExtendedProps('Patient.identifier.system')).toStrictEqual(READONLY); expect(context.getExtendedProps('Patient.identifier.value')).toStrictEqual(READONLY); }); test('setting readonly/hidden does not mutate DATA_TYPES', () => { // re-create the in-memory schema to ensure isolation from other tests indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); const schema = getDataType('Patient'); const accessPolicy: AccessPolicy = { resourceType: 'AccessPolicy', resource: [{ resourceType: 'Patient', readonlyFields: ['gender'], hiddenFields: ['identifier'] }], }; const resource: Patient = { resourceType: 'Patient', }; const apr = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); function checkSchema(): void { const typeSchema = getDataType('Patient'); expect(typeSchema.elements['gender']).toBeDefined(); expect((typeSchema.elements['gender'] as ExtendedInternalSchemaElement).readonly).toBeUndefined(); expect(typeSchema.elements['identifier']).toBeDefined(); expect((typeSchema.elements['identifier'] as ExtendedInternalSchemaElement).readonly).toBeUndefined(); } checkSchema(); const context = buildElementsContext({ elements: schema.elements, path: 'Patient', parentContext: undefined, accessPolicyResource: apr, }); if (context === undefined) { fail('Expected context to be defined'); } // schema remains unchanged checkSchema(); }); });

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