Skip to main content
Glama
match.test.ts38.4 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { SEARCH_PARAMETER_BUNDLE_FILES, readJson } from '@medplum/definitions'; import type { ActivityDefinition, Bundle, DiagnosticReport, Location, Observation, Patient, Practitioner, QuestionnaireResponse, SearchParameter, ServiceRequest, Task, } from '@medplum/fhirtypes'; import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; import { matchesSearchRequest } from './match'; import type { SearchRequest } from './search'; import { Operator, parseSearchRequest } from './search'; // Dimensions: // 1. Search parameter type // 2. Underlying data type // 3. Modifiers // 4. Prefixes // 5. Success or failure describe('Search matching', () => { beforeAll(() => { indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-medplum.json') as Bundle); for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { indexSearchParameterBundle(readJson(filename) as Bundle<SearchParameter>); } }); test('Matches resource type', () => { expect(matchesSearchRequest({ resourceType: 'Observation' } as Observation, { resourceType: 'Observation' })).toBe( true ); expect(matchesSearchRequest({ resourceType: 'Patient' }, { resourceType: 'Patient' })).toBe(true); expect(matchesSearchRequest({ resourceType: 'Observation' } as Observation, { resourceType: 'Patient' })).toBe( false ); expect(matchesSearchRequest({ resourceType: 'Patient' }, { resourceType: 'Observation' })).toBe(false); }); test('Matches _id', () => { expect( matchesSearchRequest( { resourceType: 'Project', id: '123' }, { resourceType: 'Project', filters: [{ code: '_id', operator: Operator.EQUALS, value: '123' }] } ) ).toBe(true); expect( matchesSearchRequest({ resourceType: 'Observation', id: '123' } as Observation, { resourceType: 'Observation', filters: [{ code: '_id', operator: Operator.EQUALS, value: '456' }], }) ).toBe(false); }); test('Unknown filter', () => { expect( matchesSearchRequest( { resourceType: 'Patient' }, { resourceType: 'Patient', filters: [{ code: 'unknown', operator: Operator.EQUALS, value: 'xyz' }] } ) ).toBe(false); }); test('Token filter', () => { expect( matchesSearchRequest( { resourceType: 'ProjectMembership', profile: { reference: 'Practitioner/abc123' }, project: {}, user: {} }, { resourceType: 'ProjectMembership', filters: [{ code: 'profile-type', operator: Operator.EQUALS, value: 'Practitioner' }], } ) ).toBe(true); }); test('Boolean filter', () => { expect( matchesSearchRequest( { resourceType: 'Patient', active: true }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.EQUALS, value: 'true' }] } ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'Patient', active: false }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.EQUALS, value: 'false' }] } ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'Patient', active: true }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.EQUALS, value: 'false' }] } ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'Patient', active: false }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.EQUALS, value: 'true' }] } ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'Patient', active: true }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.NOT_EQUALS, value: 'true' }] } ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'Patient', active: false }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.NOT_EQUALS, value: 'false' }] } ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'Patient', active: true }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.NOT_EQUALS, value: 'false' }] } ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'Patient', active: false }, { resourceType: 'Patient', filters: [{ code: 'active', operator: Operator.NOT_EQUALS, value: 'true' }] } ) ).toBe(true); }); test('Reference filter', () => { const resource: Observation = { resourceType: 'Observation', subject: { reference: 'Patient/123' } } as Observation; const search = { resourceType: 'Observation', filters: [{ code: 'subject', operator: Operator.EQUALS as Operator, value: 'Patient/123' }], }; search.filters[0].operator = Operator.EQUALS; search.filters[0].value = 'Patient/123'; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(true); search.filters[0].value = 'Patient/456'; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(false); search.filters[0].operator = Operator.NOT_EQUALS; search.filters[0].value = 'Patient/123'; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(false); search.filters[0].value = 'Patient/456'; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(true); }); test('Empty reference filter', () => { const resource: Observation = { resourceType: 'Observation' } as Observation; const search = { resourceType: 'Observation', filters: [{ code: 'subject', operator: Operator.EQUALS as Operator, value: '' }], }; search.filters[0].operator = Operator.EQUALS; search.filters[0].value = ''; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(true); search.filters[0].value = 'Patient/456'; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(false); search.filters[0].operator = Operator.NOT_EQUALS; search.filters[0].value = ''; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(false); search.filters[0].value = 'Patient/456'; expect(matchesSearchRequest(resource, search as SearchRequest)).toBe(true); }); test('Canonical reference filter', () => { expect( matchesSearchRequest( { resourceType: 'QuestionnaireResponse', questionnaire: 'Questionnaire/123' } as QuestionnaireResponse, { resourceType: 'QuestionnaireResponse', filters: [{ code: 'questionnaire', operator: Operator.EQUALS, value: 'Questionnaire/123' }], } ) ).toBe(true); expect( matchesSearchRequest({ resourceType: 'QuestionnaireResponse' } as QuestionnaireResponse, { resourceType: 'QuestionnaireResponse', filters: [{ code: 'questionnaire', operator: Operator.EQUALS, value: 'Questionnaire/123' }], }) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'QuestionnaireResponse', questionnaire: 'Questionnaire/123' } as QuestionnaireResponse, { resourceType: 'QuestionnaireResponse', filters: [{ code: 'questionnaire', operator: Operator.EQUALS, value: 'Questionnaire/456' }], } ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'QuestionnaireResponse', questionnaire: 'Questionnaire/123' } as QuestionnaireResponse, { resourceType: 'QuestionnaireResponse', filters: [{ code: 'questionnaire', operator: Operator.NOT_EQUALS, value: 'Questionnaire/123' }], } ) ).toBe(false); expect( matchesSearchRequest({ resourceType: 'QuestionnaireResponse' } as QuestionnaireResponse, { resourceType: 'QuestionnaireResponse', filters: [{ code: 'questionnaire', operator: Operator.NOT_EQUALS, value: 'Questionnaire/123' }], }) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'QuestionnaireResponse', questionnaire: 'Questionnaire/123' } as QuestionnaireResponse, { resourceType: 'QuestionnaireResponse', filters: [{ code: 'questionnaire', operator: Operator.NOT_EQUALS, value: 'Questionnaire/456' }], } ) ).toBe(true); }); test('String filter', () => { const patient: Patient = { resourceType: 'Patient', name: [{ given: ['Homer'], family: 'Simpson' }] }; expect( matchesSearchRequest(patient, { resourceType: 'Patient', filters: [{ code: 'name', operator: Operator.EQUALS, value: 'Simpson' }], }) ).toBe(true); expect( matchesSearchRequest(patient, { resourceType: 'Patient', filters: [{ code: 'name', operator: Operator.EQUALS, value: 'George' }], }) ).toBe(false); expect( matchesSearchRequest(patient, { resourceType: 'Patient', filters: [{ code: 'name', operator: Operator.NOT_EQUALS, value: 'Simpson' }], }) ).toBe(false); expect( matchesSearchRequest(patient, { resourceType: 'Patient', filters: [{ code: 'name', operator: Operator.NOT_EQUALS, value: 'George' }], }) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'Bot', name: 'Test Bot' }, { resourceType: 'Bot', filters: [{ code: 'name', operator: Operator.EQUALS, value: 'Test' }], } ) ).toBe(true); }); test('URI filter', () => { const activityDefinition: ActivityDefinition = { resourceType: 'ActivityDefinition', url: 'http://example.com', } as ActivityDefinition; expect( matchesSearchRequest(activityDefinition, { resourceType: 'ActivityDefinition', filters: [{ code: 'url', operator: Operator.EQUALS, value: 'http://example.com' }], }) ).toBe(true); expect( matchesSearchRequest(activityDefinition, { resourceType: 'ActivityDefinition', filters: [{ code: 'url', operator: Operator.EQUALS, value: 'http://foobar.com' }], }) ).toBe(false); }); describe('Token', () => { test('equals', () => { expect( matchesSearchRequest({ resourceType: 'Observation', code: { text: 'foo' } } as Observation, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'foo' }], }) ).toBe(true); expect( matchesSearchRequest({ resourceType: 'Observation', code: { text: 'foo' } } as Observation, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'George' }], }) ).toBe(false); }); test('not equals', () => { expect( matchesSearchRequest({ resourceType: 'Observation', code: { text: 'foo' } } as Observation, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.NOT_EQUALS, value: 'foo' }], }) ).toBe(false); expect( matchesSearchRequest({ resourceType: 'Observation', code: { text: 'foo' } } as Observation, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.NOT_EQUALS, value: 'George' }], }) ).toBe(true); }); test('not', () => { // "DiagnosticReport?status:not=cancelled" // "DiagnosticReport?status=cancelled" expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'preliminary' } as DiagnosticReport, parseSearchRequest('DiagnosticReport?status=cancelled') ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'preliminary' } as DiagnosticReport, parseSearchRequest('DiagnosticReport?status:not=cancelled') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'cancelled' } as DiagnosticReport, parseSearchRequest('DiagnosticReport?status=cancelled') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'cancelled' } as DiagnosticReport, parseSearchRequest('DiagnosticReport?status:not=cancelled') ) ).toBe(false); // "ServiceRequest?order-detail:not=VOIDED,CANCELLED" // "ServiceRequest?order-detail=VOIDED,CANCELLED" expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'ORDERED' }] } as ServiceRequest, parseSearchRequest('ServiceRequest?order-detail=VOIDED,CANCELLED') ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'ORDERED' }] } as ServiceRequest, parseSearchRequest('ServiceRequest?order-detail:not=VOIDED,CANCELLED') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'VOIDED' }] } as ServiceRequest, parseSearchRequest('ServiceRequest?order-detail=VOIDED,CANCELLED') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'VOIDED' }] } as ServiceRequest, parseSearchRequest('ServiceRequest?order-detail:not=VOIDED,CANCELLED') ) ).toBe(false); }); test('Identifier filter value', () => { const identifier = '1234567890'; const identifierSubstring = identifier.substring(0, 4); const valueOnlyValue = 'code-only'; const resource: Patient = { resourceType: 'Patient', identifier: [ { system: 'http://example.com', value: identifier, }, { system: 'http://test.com', value: identifier, }, { system: 'http://test.com', value: 'foo', }, { value: valueOnlyValue, }, ], }; // system only expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'http://example.com|' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'http://test.com|' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'http://bad.com|' }], }) ).toBe(false); // value only expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: '|' + identifier }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: '|' + identifierSubstring }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: '|' + valueOnlyValue }], }) ).toBe(true); // system and value expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'http://example.com|' + identifier }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'http://test.com|' + identifier }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: identifier }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'http://test.com|foo' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [ { code: 'identifier', operator: Operator.EQUALS, value: 'http://example.com|' + identifierSubstring }, ], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: identifierSubstring }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'bar' }], }) ).toBe(false); }); test('CodeableConcept filter value', () => { const identifier = '12345-6'; const identifierSubstring = identifier.substring(0, 4); const codeOnlyCode = 'code-only'; const resource: Observation = { resourceType: 'Observation', status: 'final', code: { coding: [ { system: 'http://example.com', code: identifier, }, { system: 'http://test.com', code: identifier, }, { system: 'http://test.com', code: 'foo', }, { code: codeOnlyCode, }, ], text: 'test', }, }; // system only expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://example.com|' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://test.com|' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://bad.com|' }], }) ).toBe(false); // code only expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: '|' + identifier }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: '|' + identifierSubstring }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: '|' + codeOnlyCode }], }) ).toBe(true); // system and code expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://example.com|' + identifier }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://test.com|' + identifier }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: identifier }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://test.com|foo' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.TEXT, value: 'test' }], }) ).toBe(true); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http://example.com|' + identifierSubstring }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: identifierSubstring }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Observation', filters: [{ code: 'code', operator: Operator.EQUALS, value: 'bar' }], }) ).toBe(false); }); }); describe('Date', () => { describe('equals', () => { test('true', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.EQUALS, value: '1990-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); test('false', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.EQUALS, value: '1990-01-02' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); }); describe('not equals', () => { test('true', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.NOT_EQUALS, value: '1990-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); test('false', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.NOT_EQUALS, value: '1990-01-02' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); }); describe('greater than', () => { test('true', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.GREATER_THAN, value: '1980-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); test('false', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.GREATER_THAN, value: '2000-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); test('same value', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.GREATER_THAN, value: '1990-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); }); describe('greater than or equals', () => { test('true', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.GREATER_THAN_OR_EQUALS, value: '1980-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); test('false', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.GREATER_THAN_OR_EQUALS, value: '2000-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); test('same value', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.GREATER_THAN_OR_EQUALS, value: '1990-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); }); describe('less than', () => { test('true', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.LESS_THAN, value: '1980-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); test('false', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.LESS_THAN, value: '2000-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); test('same value', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.LESS_THAN, value: '1990-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); }); describe('less than or equals', () => { test('true', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.LESS_THAN_OR_EQUALS, value: '1980-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(false); }); test('false', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.LESS_THAN_OR_EQUALS, value: '2000-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); test('same value', () => { const patient: Patient = { resourceType: 'Patient', birthDate: '1990-01-01' }; const search: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'birthdate', operator: Operator.LESS_THAN_OR_EQUALS, value: '1990-01-01' }], }; expect(matchesSearchRequest(patient, search)).toBe(true); }); }); }); describe('Period', () => { describe('missing', () => { const task: Task = { resourceType: 'Task', status: 'accepted', intent: 'order', }; test('true', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '2025-05-01' }], }; expect(matchesSearchRequest(task, search)).toBe(false); }); test('false', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '2025-06-01' }], }; expect(matchesSearchRequest(task, search)).toBe(false); }); }); describe('invalid', () => { const task: Task = { resourceType: 'Task', status: 'accepted', intent: 'order', restriction: { period: { start: '2025-05-15T12:00:00.000Z' } }, }; test('true', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '.' }], }; expect(matchesSearchRequest(task, search)).toBe(false); }); test('false', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '.' }], }; expect(matchesSearchRequest(task, search)).toBe(false); }); }); describe('start greater than', () => { const task: Task = { resourceType: 'Task', status: 'accepted', intent: 'order', restriction: { period: { start: '2025-05-15T12:00:00.000Z', end: '2025-05-15T13:00:00.000Z', }, }, }; test('true', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '2025-05-01' }], }; expect(matchesSearchRequest(task, search)).toBe(true); }); test('false', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '2025-06-01' }], }; expect(matchesSearchRequest(task, search)).toBe(false); }); }); describe('end greater than', () => { const task: Task = { resourceType: 'Task', status: 'accepted', intent: 'order', restriction: { period: { end: '2025-05-15T12:00:00.000Z' } }, }; test('true', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '2025-05-01' }], }; expect(matchesSearchRequest(task, search)).toBe(true); }); test('false', () => { const search: SearchRequest = { resourceType: 'Task', filters: [{ code: 'due-date', operator: Operator.GREATER_THAN, value: '2025-06-01' }], }; expect(matchesSearchRequest(task, search)).toBe(false); }); }); }); test('Compartments', () => { const resource1: Patient = { resourceType: 'Patient', meta: { compartment: [{ reference: 'Organization/123' }] }, }; const resource2: Patient = { resourceType: 'Patient', meta: { compartment: [{ reference: 'Organization/456' }] }, }; const search1: SearchRequest = { resourceType: 'Patient', filters: [{ code: '_compartment', operator: Operator.EQUALS, value: 'Organization/123' }], }; const search2: SearchRequest = { resourceType: 'Patient', filters: [{ code: '_compartment', operator: Operator.EQUALS, value: 'Organization/456' }], }; // Backwards compatibility // Support matching values without the resourceType prefix const search3: SearchRequest = { resourceType: 'Patient', filters: [{ code: '_compartment', operator: Operator.EQUALS, value: '123' }], }; expect(matchesSearchRequest(resource1, search1)).toBe(true); expect(matchesSearchRequest(resource1, search2)).toBe(false); expect(matchesSearchRequest(resource1, search3)).toBe(true); expect(matchesSearchRequest(resource2, search1)).toBe(false); expect(matchesSearchRequest(resource2, search2)).toBe(true); expect(matchesSearchRequest(resource2, search3)).toBe(false); }); test('Identifier', () => { const identifier = '1234567890'; const resource: Patient = { resourceType: 'Patient', identifier: [ { system: 'test', value: identifier, }, ], }; const search1: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: identifier }], }; expect(matchesSearchRequest(resource, search1)).toBe(true); const search2: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'foo' }], }; expect(matchesSearchRequest(resource, search2)).toBe(false); }); test('Identifier with system', () => { const identifier = '1234567890'; const resource: Practitioner = { resourceType: 'Practitioner', identifier: [ { system: 'https://example.com', value: identifier, }, ], }; const search1: SearchRequest = { resourceType: 'Practitioner', filters: [{ code: 'identifier', operator: Operator.EQUALS, value: 'https://example.com|' + identifier }], }; expect(matchesSearchRequest(resource, search1)).toBe(true); }); test(':missing', () => { const resource: Patient = { resourceType: 'Patient', }; const search1: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'organization', operator: Operator.MISSING, value: 'true' }], }; expect(matchesSearchRequest(resource, search1)).toBe(true); resource.managingOrganization = { reference: 'Organization/FooMedical', }; const search2: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'organization', operator: Operator.MISSING, value: 'false' }], }; expect(matchesSearchRequest(resource, search2)).toBe(true); }); test('Present', () => { const resource: Patient = { resourceType: 'Patient', }; const search1: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'organization', operator: Operator.PRESENT, value: 'true' }], }; expect(matchesSearchRequest(resource, search1)).toBe(false); resource.managingOrganization = { reference: 'Organization/FooMedical', }; const search2: SearchRequest = { resourceType: 'Patient', filters: [{ code: 'organization', operator: Operator.PRESENT, value: 'false' }], }; expect(matchesSearchRequest(resource, search2)).toBe(false); }); test('Meta.tag', () => { // This test demonstrates the bug where partial matching is incorrectly allowed // for meta.tag searches when only exact matches should be permitted const resource: Patient = { resourceType: 'Patient', meta: { tag: [ { system: 'http://example.com/tags', code: 'SENSITIVE', display: 'Sensitive Patient Data', }, { system: 'http://example.com/tags', code: 'VIP', display: 'VIP Patient', }, { code: 'EMERGENCY', // tag without system }, ], }, }; // Should match exact code expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'SENSITIVE' }], }) ).toBe(true); // Should match exact system|code expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'http://example.com/tags|SENSITIVE' }], }) ).toBe(true); // Should match system only (system|) expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'http://example.com/tags|' }], }) ).toBe(true); // CURRENT BUG: This currently matches due to partial matching (SENS is substring of SENSITIVE) // but should NOT match - only exact matches should be allowed for token searches expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'SENS' }], }) ).toBe(false); // This test will currently FAIL due to the bug // CURRENT BUG: This currently matches due to partial matching // but should NOT match - only exact matches should be allowed expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'PATIENT' }], }) ).toBe(false); // This test will currently FAIL due to the bug // Should NOT match different exact code expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'RESTRICTED' }], }) ).toBe(false); // Should NOT match wrong system expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.EQUALS, value: 'http://other.com/tags|SENSITIVE' }], }) ).toBe(false); // NOT_EQUALS tests expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.NOT_EQUALS, value: 'SENSITIVE' }], }) ).toBe(false); expect( matchesSearchRequest(resource, { resourceType: 'Patient', filters: [{ code: '_tag', operator: Operator.NOT_EQUALS, value: 'RESTRICTED' }], }) ).toBe(true); }); test('Special not implemented', () => { const resource: Location = { resourceType: 'Location', }; const search1: SearchRequest = { resourceType: 'Location', filters: [{ code: 'near', operator: Operator.EQUALS, value: 'foo' }], }; expect(matchesSearchRequest(resource, search1)).toBe(false); }); });

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