// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
// SPDX-License-Identifier: Apache-2.0
import { ContentType } from '@medplum/core';
import type { ConceptMap, OperationOutcome, Parameters, ParametersParameter } from '@medplum/fhirtypes';
import express from 'express';
import request from 'supertest';
import { initApp, shutdownApp } from '../../app';
import { loadTestConfig } from '../../config/loader';
import { initTestAuth } from '../../test.setup';
const app = express();
describe('ConceptMap $translate', () => {
const system = 'http://example.com/private-codes';
const code = 'FSH';
let accessToken: string;
let conceptMap: ConceptMap;
beforeAll(async () => {
const config = await loadTestConfig();
await initApp(app, config);
});
beforeEach(async () => {
accessToken = await initTestAuth();
expect(accessToken).toBeDefined();
const res = await request(app)
.post(`/fhir/R4/ConceptMap`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'ConceptMap',
url: 'http://example.com/concept-map',
status: 'active',
sourceCanonical: 'http://example.com/labs',
group: [
{
source: system,
target: 'http://loinc.org',
element: [
{
code,
target: [
{
code: '15067-2',
display: 'Follitropin Qn',
equivalence: 'equivalent',
},
],
},
],
},
{
source: system,
target: 'http://www.ama-assn.org/go/cpt',
element: [
{
code,
target: [{ code: '83001', equivalence: 'equivalent' }],
},
],
},
],
});
expect(res.status).toStrictEqual(201);
conceptMap = res.body as ConceptMap;
});
afterAll(async () => {
await shutdownApp();
});
test.each<[string, ParametersParameter[]]>([
[
'with system and code',
[
{ name: 'system', valueUri: system },
{ name: 'code', valueCode: code },
],
],
['with coding', [{ name: 'coding', valueCoding: { system, code } }]],
['with CodeableConcept', [{ name: 'codeableConcept', valueCodeableConcept: { coding: [{ system, code }] } }]],
])('Success %s', async (_format, params) => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: params,
});
expect(res.status).toBe(200);
const output = (res.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(2);
const loincMatch = matches?.find((m) => m.part?.[1]?.valueCoding?.system === 'http://loinc.org');
const cptMatch = matches?.find((m) => m.part?.[1]?.valueCoding?.system === 'http://www.ama-assn.org/go/cpt');
expect(loincMatch).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://loinc.org',
code: '15067-2',
display: 'Follitropin Qn',
},
},
],
});
expect(cptMatch).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://www.ama-assn.org/go/cpt',
code: '83001',
},
},
],
});
});
test('Lookup by URL', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'url', valueUri: conceptMap.url },
{ name: 'coding', valueCoding: { system, code } },
],
});
expect(res.status).toBe(200);
const output = (res.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(2);
const loincMatch = matches?.find((m) => m.part?.[1]?.valueCoding?.system === 'http://loinc.org');
const cptMatch = matches?.find((m) => m.part?.[1]?.valueCoding?.system === 'http://www.ama-assn.org/go/cpt');
expect(loincMatch).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://loinc.org',
code: '15067-2',
display: 'Follitropin Qn',
},
},
],
});
expect(cptMatch).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://www.ama-assn.org/go/cpt',
code: '83001',
},
},
],
});
});
test('Allows GET request', async () => {
const res = await request(app)
.get(`/fhir/R4/ConceptMap/$translate?url=${conceptMap.url}&system=${system}&code=${code}`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send();
expect(res.status).toBe(200);
const output = (res.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(2);
});
test('Lookup by source ValueSet', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'source', valueUri: conceptMap.sourceCanonical },
{ name: 'coding', valueCoding: { system, code } },
],
});
expect(res.status).toBe(200);
const output = (res.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(2);
const loincMatch = matches?.find((m) => m.part?.[1]?.valueCoding?.system === 'http://loinc.org');
const cptMatch = matches?.find((m) => m.part?.[1]?.valueCoding?.system === 'http://www.ama-assn.org/go/cpt');
expect(loincMatch).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://loinc.org',
code: '15067-2',
display: 'Follitropin Qn',
},
},
],
});
expect(cptMatch).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://www.ama-assn.org/go/cpt',
code: '83001',
},
},
],
});
});
test('Filter on target system', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'url', valueUri: conceptMap.url },
{ name: 'targetsystem', valueUri: 'http://loinc.org' },
{ name: 'coding', valueCoding: { system, code } },
],
});
expect(res.status).toBe(200);
const output = (res.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(1);
expect(matches?.[0]).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://loinc.org',
code: '15067-2',
display: 'Follitropin Qn',
},
},
],
});
});
test('No match', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system, code: 'BAD' } }],
});
expect(res.status).toBe(200);
const output = (res.body as Parameters).parameter;
expect(output).toHaveLength(1);
expect(output?.[0]).toMatchObject<ParametersParameter>({
name: 'result',
valueBoolean: false,
});
});
test('No map specified', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system, code } }],
});
expect(res.status).toBe(400);
expect(res.body).toMatchObject({
resourceType: 'OperationOutcome',
issue: [
{
details: { text: 'No ConceptMap specified' },
},
],
});
});
test('Code without system', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'code', valueCode: 'BAD' }],
});
expect(res.status).toBe(400);
expect(res.body).toMatchObject({
resourceType: 'OperationOutcome',
issue: [{ details: { text: 'System parameter must be provided with code' } }],
});
});
test('Ambiguous coding provided', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'coding', valueCoding: { system, code } },
{ name: 'system', valueUri: system },
{ name: 'code', valueCode: 'BAD' },
],
});
expect(res.status).toBe(400);
expect(res.body).toMatchObject<OperationOutcome>({
resourceType: 'OperationOutcome',
issue: [
{
severity: 'error',
code: 'invalid',
details: { text: `Ambiguous input: multiple source codings provided` },
},
],
});
});
test('No source coding', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
});
expect(res.status).toBe(400);
expect(res.body).toMatchObject({
resourceType: 'OperationOutcome',
issue: [{ details: { text: 'Source Coding (system + code) must be specified' } }],
});
});
test.each(['url', 'source'])('Not found by %s', async (paramName) => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [
{ name: paramName, valueUri: conceptMap.url + 'BAD' },
{ name: 'coding', valueCoding: { system, code } },
],
});
expect(res.status).toBe(400);
expect(res.body).toMatchObject({
resourceType: 'OperationOutcome',
issue: [
{
details: {
text: expect.stringMatching(/^ConceptMap .* not found$/),
},
},
],
});
});
test('Unmapped code handling', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'ConceptMap',
url: 'http://example.com/concept-map',
status: 'active',
sourceCanonical: 'http://example.com/labs',
group: [
{
source: system,
target: system + '/v2',
element: [
{
code: 'OTHER',
target: [{ code: 'DISTINCT', equivalence: 'equivalent' }],
},
],
unmapped: {
mode: 'provided',
},
},
{
source: system,
target: 'http://example.com/other-system',
element: [
{
code: 'OTHER',
target: [{ code: '1', equivalence: 'equivalent' }],
},
],
unmapped: {
mode: 'fixed',
code: 'UNK',
display: 'Unknown',
},
},
],
});
expect(res.status).toStrictEqual(201);
conceptMap = res.body as ConceptMap;
const res2 = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system, code } }],
});
expect(res2.status).toBe(200);
const output = (res2.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(2);
expect(matches?.[0]).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equal',
},
{
name: 'concept',
valueCoding: { system: system + '/v2', code },
},
],
});
expect(matches?.[1]).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://example.com/other-system',
code: 'UNK',
display: 'Unknown',
},
},
],
});
});
test('Handles empty CodeableConcept', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'codeableConcept', valueCodeableConcept: { text: 'Nebulous concept' } }],
});
expect(res.status).toBe(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{
name: 'result',
valueBoolean: false,
},
],
});
});
test('Handles implicit system', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'ConceptMap',
url: 'http://example.com/concept-map',
status: 'active',
sourceCanonical: 'http://example.com/labs',
group: [
{
target: 'http://loinc.org',
element: [
{
code,
target: [
{
code: '15067-2',
display: 'Follitropin Qn',
equivalence: 'equivalent',
},
],
},
],
},
],
});
expect(res.status).toStrictEqual(201);
conceptMap = res.body as ConceptMap;
const res2 = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'codeableConcept', valueCodeableConcept: { coding: [{ code }] } }],
});
expect(res2.status).toBe(200);
const output = (res2.body as Parameters).parameter;
expect(output?.find((p) => p.name === 'result')?.valueBoolean).toStrictEqual(true);
const matches = output?.filter((p) => p.name === 'match');
expect(matches).toHaveLength(1);
expect(matches?.[0]).toMatchObject<ParametersParameter>({
name: 'match',
part: [
{
name: 'equivalence',
valueCode: 'equivalent',
},
{
name: 'concept',
valueCoding: {
system: 'http://loinc.org',
code: '15067-2',
display: 'Follitropin Qn',
},
},
],
});
});
test('No mapping groups specified', async () => {
const res = await request(app)
.post(`/fhir/R4/ConceptMap`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'ConceptMap',
url: 'http://example.com/concept-map',
status: 'active',
sourceCanonical: 'http://example.com/labs',
targetCanonical: 'http://example.com/loinc',
});
expect(res.status).toStrictEqual(201);
conceptMap = res.body as ConceptMap;
const res2 = await request(app)
.post(`/fhir/R4/ConceptMap/${conceptMap.id}/$translate`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system, code } }],
});
expect(res2.status).toBe(200);
expect(res2.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [{ name: 'result', valueBoolean: false }],
});
});
});