Skip to main content
Glama
repo.test.ts9.6 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { OperationOutcomeError, WithId } from '@medplum/core'; import { badRequest, createReference, getReferenceString, indexSearchParameterBundle, indexStructureDefinitionBundle, notFound, parseSearchRequest, } from '@medplum/core'; import { readJson } from '@medplum/definitions'; import type { Bundle, Observation, Patient, Resource, ResourceType, SearchParameter } from '@medplum/fhirtypes'; import { randomInt, randomUUID } from 'crypto'; import { MemoryRepository } from './repo'; const repo = new MemoryRepository(); describe('MemoryRepository', () => { beforeAll(async () => { indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); indexSearchParameterBundle(readJson('fhir/r4/search-parameters.json') as Bundle<SearchParameter>); }); test('Create resource', async () => { const patient = await repo.createResource<Patient>({ resourceType: 'Patient' }); expect(patient.id).toBeDefined(); expect(patient.meta?.versionId).toBeDefined(); expect(patient.meta?.lastUpdated).toBeDefined(); }); test('Create resource with meta', async () => { const id = randomUUID(); const versionId = randomUUID(); const lastUpdated = new Date().toISOString(); const patient = await repo.createResource<Patient>({ resourceType: 'Patient', id, meta: { versionId, lastUpdated }, }); expect(patient.id).toBe(id); expect(patient.meta?.versionId).toBe(versionId); expect(patient.meta?.lastUpdated).toBe(lastUpdated); }); test('Read invalid reference', async () => { try { await repo.readReference({}); fail('Expected error'); } catch (err) { const outcome = (err as OperationOutcomeError).outcome; expect(outcome).toMatchObject(badRequest('Invalid reference')); } }); test('Read version', async () => { const patient = await repo.createResource<Patient>({ resourceType: 'Patient' }); expect(patient.id).toBeDefined(); const patient2 = await repo.readVersion<Patient>('Patient', patient.id, patient.meta?.versionId as string); expect(patient2.id).toBe(patient.id); try { await repo.readVersion<Patient>('Patient', patient.id, randomUUID()); fail('Expected error'); } catch (err) { const outcome = (err as OperationOutcomeError).outcome; expect(outcome).toMatchObject(notFound); } }); test('Count and offset', async () => { for (let i = 0; i < 10; i++) { await repo.createResource<Observation>({ resourceType: 'Observation' } as Observation); } const bundle = await repo.search({ resourceType: 'Observation', offset: 1, count: 1 }); expect(bundle.entry).toHaveLength(1); }); test('searchResources helper', async () => { const family = randomUUID(); const patient = await repo.createResource<Patient>({ resourceType: 'Patient', name: [{ family }] }); const resources = await repo.searchResources<Patient>(parseSearchRequest('Patient?family=' + family)); expect(resources).toHaveLength(1); expect(resources[0].id).toBe(patient.id); const emptyResources = await repo.searchResources<Patient>(parseSearchRequest('Patient?family=' + randomUUID())); expect(emptyResources).toHaveLength(0); }); test('searchOne helper', async () => { const family = randomUUID(); const patient = await repo.createResource<Patient>({ resourceType: 'Patient', name: [{ family }] }); const resource = await repo.searchOne<Patient>(parseSearchRequest('Patient?family=' + family)); expect(resource?.id).toBe(patient.id); const emptyResource = await repo.searchOne<Patient>(parseSearchRequest('Patient?family=' + randomUUID())); expect(emptyResource).toBeUndefined(); }); test('Sort unknown search parameter', async () => { for (let i = 0; i < 10; i++) { await repo.createResource<Patient>({ resourceType: 'Patient' }); } // Mock repository silently ignores unknown search parameters // This is different than the real server environment, which will throw an error const bundle = await repo.search({ resourceType: 'Patient', sortRules: [{ code: 'xyz' }] }); expect(bundle.entry).toBeDefined(); }); test('clears all resources', async () => { repo.clear(); const resourceTypes: ResourceType[] = ['Patient', 'Observation', 'AccessPolicy', 'Account', 'Binary', 'Bot']; let expectedTotalResourceCount = 0; await Promise.all( resourceTypes.map(async (rt) => { const count = randomInt(9) + 1; for (let i = 0; i < count; i++) { await repo.createResource<any>({ resourceType: rt }); } expectedTotalResourceCount += count; }) ); const resourcesListBefore = await Promise.all( resourceTypes.map((rt) => repo.searchResources({ resourceType: rt })) ); const actualResourceCountBefore = resourcesListBefore.reduce((count, resources) => (count += resources.length), 0); expect(actualResourceCountBefore).toBe(expectedTotalResourceCount); repo.clear(); const resourcesListAfter = await Promise.all(resourceTypes.map((rt) => repo.searchResources({ resourceType: rt }))); const actualResourceCountAfter = resourcesListAfter.reduce((count, resources) => (count += resources.length), 0); expect(actualResourceCountAfter).toBe(0); }); describe('searchByReference', () => { async function createPatients(repo: MemoryRepository, count: number): Promise<WithId<Patient>[]> { const patients = []; for (let i = 0; i < count; i++) { patients.push(await repo.createResource<Patient>({ resourceType: 'Patient' })); } return patients; } async function createObservations( repo: MemoryRepository, count: number, patient: Patient ): Promise<WithId<Observation>[]> { const resources = []; for (let i = 0; i < count; i++) { resources.push( await repo.createResource<Observation>({ resourceType: 'Observation', subject: createReference(patient), valueString: i.toString(), } as Observation) ); } return resources; } function zip<A, B>(a: A[], b: B[]): [A, B][] { return a.map((k, i) => [k, b[i]]); } function expectResultsContents<Parent extends Resource, Child extends Resource>( parents: Parent[], childrenByParent: Child[][], { count, offset }: { count: number; offset: number }, results: Record<string, Child[]> ): void { expect(Object.keys(results)).toHaveLength(parents.length); for (const [parent, children] of zip(parents, childrenByParent)) { const result = results[getReferenceString(parent) as string]; expect(result).toHaveLength(Math.min(children.length - offset, count)); for (const child of result) { expect(children.map((c) => c.id)).toContain(child.id); } } } test('basic search by reference', async () => { const patients = await createPatients(repo, 3); const patientObservations = [ await createObservations(repo, 2, patients[0]), await createObservations(repo, 3, patients[1]), await createObservations(repo, 4, patients[2]), ]; const count = 3; const offset = 1; const observation = patientObservations[0][offset]; const result = await repo.searchByReference<Observation>( { resourceType: 'Observation', count, offset }, 'subject', patients.map((p) => getReferenceString(p)) ); expectResultsContents(patients, patientObservations, { count, offset }, result); const resultRepoObservation = result[getReferenceString(patients[0])][0]; expect(resultRepoObservation).toStrictEqual(observation); expect(resultRepoObservation.meta?.tag).toBeUndefined(); }); test('with sort', async () => { const patients = await createPatients(repo, 2); const patientObservations = [ await createObservations(repo, 3, patients[0]), await createObservations(repo, 2, patients[1]), ]; const count = 3; const offset = 0; // descending const resultDesc = await repo.searchByReference<Observation>( { resourceType: 'Observation', count, offset, sortRules: [{ code: 'value-string', descending: true }] }, 'subject', patients.map((p) => getReferenceString(p)) ); expectResultsContents(patients, patientObservations, { count, offset }, resultDesc); expect(resultDesc[getReferenceString(patients[0])].map((o) => o.valueString)).toStrictEqual(['2', '1', '0']); expect(resultDesc[getReferenceString(patients[1])].map((o) => o.valueString)).toStrictEqual(['1', '0']); // ascending const resultAsc = await repo.searchByReference<Observation>( { resourceType: 'Observation', count, sortRules: [{ code: 'value-string', descending: false }] }, 'subject', patients.map((p) => getReferenceString(p)) ); expectResultsContents(patients, patientObservations, { count, offset }, resultAsc); expect(resultAsc[getReferenceString(patients[0])].map((o) => o.valueString)).toStrictEqual(['0', '1', '2']); expect(resultAsc[getReferenceString(patients[1])].map((o) => o.valueString)).toStrictEqual(['0', '1']); }); }); });

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