Skip to main content
Glama
reference.test.ts9.72 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { WithId } from '@medplum/core'; import type { Observation, Patient, Practitioner, ServiceRequest } from '@medplum/fhirtypes'; import { randomUUID } from 'node:crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config/loader'; import { DatabaseMode } from '../../database'; import { getSystemRepo } from '../repo'; import { lookupTables } from '../searchparameter'; import type { ReferenceTableRow } from './reference'; import { ReferenceTable } from './reference'; describe('ReferenceTable', () => { const systemRepo = getSystemRepo(); let refTable: ReferenceTable; beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); const maybeTable = lookupTables.find((table) => table instanceof ReferenceTable); if (!maybeTable) { throw new Error('ReferenceTable not found in lookupTables'); } refTable = maybeTable; }); afterAll(async () => { await shutdownApp(); }); function sortFn(a: ReferenceTableRow, b: ReferenceTableRow): number { return a.code.localeCompare(b.code); } describe('getColumnName', () => { test('throws not implemented error', () => { expect(() => refTable.getColumnName()).toThrow('ReferenceTable.getColumnName not implemented'); }); }); describe('getExistingRows', () => { test('returns empty array for empty resources', async () => { const rows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), []); expect(rows).toEqual([]); }); }); describe('batchInsertRows', () => { test('returns early for empty values without querying DB', async () => { const client = systemRepo.getDatabaseClient(DatabaseMode.WRITER); const querySpy = jest.spyOn(client, 'query'); await refTable.batchInsertRows(client, 'Observation', []); expect(querySpy).not.toHaveBeenCalled(); querySpy.mockRestore(); }); }); describe('batchIndexResources', () => { test('returns early for empty resources array', async () => { const client = systemRepo.getDatabaseClient(DatabaseMode.WRITER); const querySpy = jest.spyOn(client, 'query'); // Should not throw and should return early await expect(refTable.batchIndexResources(client, [], true)).resolves.toBeUndefined(); expect(querySpy).not.toHaveBeenCalled(); querySpy.mockRestore(); }); test('create, update, delete', async () => { const patient1 = randomUUID(); const patient2 = randomUUID(); const encounterId = randomUUID(); const obs = await systemRepo.createResource<Observation>({ resourceType: 'Observation', subject: { reference: 'Patient/' + patient1 }, status: 'registered', code: { coding: [{ system: 'http://loinc.org', code: '3141-9' }] }, }); const createRows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [obs]); expect(createRows).toHaveLength(2); expect(createRows.sort(sortFn)).toStrictEqual([ { resourceId: obs.id, code: 'patient', targetId: patient1, }, { resourceId: obs.id, code: 'subject', targetId: patient1, }, ]); await systemRepo.updateResource<Observation>({ ...obs, subject: { reference: 'Patient/' + patient2 }, encounter: { reference: 'Encounter/' + encounterId }, }); const updateRows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [obs]); expect(updateRows).toHaveLength(3); expect(updateRows.sort(sortFn)).toStrictEqual([ { resourceId: obs.id, code: 'encounter', targetId: encounterId, }, { resourceId: obs.id, code: 'patient', targetId: patient2, }, { resourceId: obs.id, code: 'subject', targetId: patient2, }, ]); await systemRepo.deleteResource('Observation', obs.id); const deleteRows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [obs]); expect(deleteRows).toHaveLength(0); }); test('throws error for mixed resource types', async () => { const obs: WithId<Observation> = { resourceType: 'Observation', id: randomUUID(), status: 'registered', code: { coding: [{ system: 'http://loinc.org', code: '3141-9' }] }, }; const patient: WithId<Patient> = { resourceType: 'Patient', id: randomUUID(), }; await expect( refTable.batchIndexResources(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [obs, patient], true) ).rejects.toThrow('batchIndexResources must be called with resources of the same type: Patient vs Observation'); }); test('handles update with unchanged references', async () => { const patientId = randomUUID(); const obs = await systemRepo.createResource<Observation>({ resourceType: 'Observation', subject: { reference: 'Patient/' + patientId }, status: 'registered', code: { coding: [{ system: 'http://loinc.org', code: '3141-9' }] }, }); // Update with the same reference - should detect no changes await systemRepo.updateResource<Observation>({ ...obs, status: 'final', // Change something else, not the reference }); const rows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [obs]); expect(rows).toHaveLength(2); expect(rows.sort(sortFn)).toStrictEqual([ { resourceId: obs.id, code: 'patient', targetId: patientId, }, { resourceId: obs.id, code: 'subject', targetId: patientId, }, ]); }); test('yields between batches for many resources', async () => { const patientId = randomUUID(); // Create more resources than the batch size to test yielding const batchSize = 2; const resources: WithId<Observation>[] = []; for (let i = 0; i < batchSize + 1; i++) { resources.push({ resourceType: 'Observation', id: randomUUID(), subject: { reference: 'Patient/' + patientId }, status: 'registered', code: { coding: [{ system: 'http://loinc.org', code: '3141-9' }] }, }); } // This should process all resources with yielding between batches await expect( refTable.batchIndexResources(systemRepo.getDatabaseClient(DatabaseMode.WRITER), resources, true, batchSize) ).resolves.toBeUndefined(); // Verify at least one resource was indexed const rows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [resources[0]]); expect(rows.length).toBeGreaterThan(0); }); }); describe('extractValues', () => { test('logs and rethrows error when extraction fails', async () => { const obs: WithId<Observation> = { resourceType: 'Observation', id: randomUUID(), status: 'registered', code: { coding: [{ system: 'http://loinc.org', code: '3141-9' }] }, }; const extractError = new Error('Test extraction error'); const extractValuesSpy = jest.spyOn(refTable, 'extractValues').mockImplementation(() => { throw extractError; }); await expect( refTable.batchIndexResources(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [obs], true) ).rejects.toThrow('Test extraction error'); extractValuesSpy.mockRestore(); }); test('handles resource with contained resource reference', async () => { const containedPractitionerId = randomUUID(); // Create a ServiceRequest with a contained Practitioner const serviceRequest = await systemRepo.createResource<ServiceRequest>({ resourceType: 'ServiceRequest', status: 'active', intent: 'order', subject: { reference: 'Patient/' + randomUUID() }, contained: [ { resourceType: 'Practitioner', id: containedPractitionerId, name: [{ text: 'Dr. Test' }], } as Practitioner, ], requester: { reference: '#' + containedPractitionerId }, }); // The requester reference uses a local reference to contained resource // This tests that references are properly extracted const rows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [serviceRequest]); expect(rows.length).toBeGreaterThan(0); // Verify the subject reference was indexed (patient reference) const subjectRow = rows.find((r) => r.code === 'subject'); expect(subjectRow).toBeDefined(); }); test('handles resource with no reference search parameters', async () => { // Patient has reference search params like general-practitioner, organization, etc. // But creating a minimal Patient with no references should still work const patient = await systemRepo.createResource<Patient>({ resourceType: 'Patient', name: [{ text: 'Test Patient' }], }); const rows = await refTable.getExistingRows(systemRepo.getDatabaseClient(DatabaseMode.WRITER), [patient]); // Patient with no references should have no reference rows expect(rows).toHaveLength(0); }); }); });

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