// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
// SPDX-License-Identifier: Apache-2.0
import type { WithId } from '@medplum/core';
import { ContentType } from '@medplum/core';
import type { CodeSystem, OperationOutcome, Parameters } from '@medplum/fhirtypes';
import { randomUUID } from 'crypto';
import express from 'express';
import request from 'supertest';
import { initApp, shutdownApp } from '../../app';
import { loadTestConfig } from '../../config/loader';
import { initTestAuth } from '../../test.setup';
import { validateCodings } from './codesystemvalidatecode';
const app = express();
const testCodeSystem: CodeSystem = {
resourceType: 'CodeSystem',
url: 'http://example.com/test-code-system-' + randomUUID(),
name: 'testCodeSystem',
title: 'Test Code System',
status: 'active',
hierarchyMeaning: 'is-a',
content: 'not-present',
};
describe('CodeSystem validate-code', () => {
let codeSystem: WithId<CodeSystem>;
let accessToken: string;
beforeAll(async () => {
const config = await loadTestConfig();
await initApp(app, config);
accessToken = await initTestAuth({ superAdmin: true });
expect(accessToken).toBeDefined();
const res = await request(app)
.post('/fhir/R4/CodeSystem')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send(testCodeSystem);
expect(res.status).toStrictEqual(201);
codeSystem = res.body;
const res2 = await request(app)
.post(`/fhir/R4/CodeSystem/$import`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'system', valueUri: codeSystem.url },
{ name: 'concept', valueCoding: { code: '1', display: 'Biopsy of brain' } },
{ name: 'concept', valueCoding: { code: '2', display: 'Biopsy of head' } },
],
} as Parameters);
expect(res2.status).toBe(200);
});
afterAll(async () => {
await shutdownApp();
});
test('Success', async () => {
const res = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'url', valueUri: codeSystem.url },
{ name: 'code', valueCode: '1' },
],
} as Parameters);
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{ name: 'result', valueBoolean: true },
{ name: 'display', valueString: 'Biopsy of brain' },
],
});
});
test('Coding parameter', async () => {
const res = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system: codeSystem.url, code: '1' } }],
} as Parameters);
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{ name: 'result', valueBoolean: true },
{ name: 'display', valueString: 'Biopsy of brain' },
],
});
});
test('Not found', async () => {
const res = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'url', valueUri: codeSystem.url },
{ name: 'code', valueCode: 'wrong code' },
],
} as Parameters);
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [{ name: 'result', valueBoolean: false }],
});
});
test('No full coding specified', async () => {
const res = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [{ name: 'code', valueCode: 'wrong code' }],
} as Parameters);
expect(res.status).toStrictEqual(400);
expect(res.body).toMatchObject<OperationOutcome>({
resourceType: 'OperationOutcome',
issue: [{ severity: 'error', code: 'invalid', details: { text: 'No code system specified' } }],
});
});
test('Checks project', async () => {
const otherAccessToken = await initTestAuth();
const res = await request(app)
.post(`/fhir/R4/CodeSystem/${codeSystem.id}/$validate-code`)
.set('Authorization', 'Bearer ' + otherAccessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system: codeSystem.url, code: '1' } }],
} as Parameters);
expect(res.status).toBe(404);
});
test('Falls back to validating system URL', async () => {
// System URL doesn't have a corresponding CodeSystem resource
const system = 'https://example.com/' + randomUUID();
// Test with matching system URL
const resY = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system, code: '1' } }],
} as Parameters);
expect(resY.status).toBe(200);
expect(resY.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [{ name: 'result', valueBoolean: true }],
});
// Test with system URL that doesn't match
const resN = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'url', valueUri: 'http://example.com/other-system' },
{ name: 'coding', valueCoding: { system, code: '1' } },
],
} as Parameters);
expect(resN.status).toBe(200);
expect(resN.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [{ name: 'result', valueBoolean: false }],
});
});
test('Lookup using specific CodeSystem version', async () => {
const updatedCodeSystem: CodeSystem = {
...testCodeSystem,
content: 'complete',
version: '3.1.4',
concept: [{ code: '5', display: 'Neologism' }],
};
const res = await request(app)
.post('/fhir/R4/CodeSystem')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', ContentType.FHIR_JSON)
.send(updatedCodeSystem);
expect(res.status).toStrictEqual(201);
const codeSystem = res.body as CodeSystem;
const res2 = await request(app)
.post('/fhir/R4/CodeSystem/$validate-code')
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [
{ name: 'coding', valueCoding: { system: codeSystem.url, code: '5' } },
{ name: 'version', valueString: '3.1.4' },
],
} as Parameters);
expect(res2.status).toStrictEqual(200);
expect(res2.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{ name: 'result', valueBoolean: true },
{ name: 'display', valueString: 'Neologism' },
],
});
});
test('GET endpoint', async () => {
const res = await request(app)
.get(`/fhir/R4/CodeSystem/$validate-code?url=${codeSystem.url}&code=1`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send();
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{ name: 'result', valueBoolean: true },
{ name: 'display', valueString: 'Biopsy of brain' },
],
});
});
test('GET instance endpoint', async () => {
const res = await request(app)
.get(`/fhir/R4/CodeSystem/${codeSystem.id}/$validate-code?code=1`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send();
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{ name: 'result', valueBoolean: true },
{ name: 'display', valueString: 'Biopsy of brain' },
],
});
});
test('Fail on instance system URL mismatch', async () => {
const res = await request(app)
.get(`/fhir/R4/CodeSystem/${codeSystem.id}/$validate-code?url=incorrect&code=1`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send();
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [{ name: 'result', valueBoolean: false }],
});
});
test('Instance endpoint with coding', async () => {
const res = await request(app)
.post(`/fhir/R4/CodeSystem/${codeSystem.id}/$validate-code`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { code: '1' } }],
} as Parameters);
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [
{ name: 'result', valueBoolean: true },
{ name: 'display', valueString: 'Biopsy of brain' },
],
});
});
test('Fail on instance coding system mismatch', async () => {
const res = await request(app)
.post(`/fhir/R4/CodeSystem/${codeSystem.id}/$validate-code`)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/fhir+json')
.send({
resourceType: 'Parameters',
parameter: [{ name: 'coding', valueCoding: { system: 'incorrect', code: '1' } }],
} as Parameters);
expect(res.status).toStrictEqual(200);
expect(res.body).toMatchObject<Parameters>({
resourceType: 'Parameters',
parameter: [{ name: 'result', valueBoolean: false }],
});
});
test('validateCodings', async () => {
const result = await validateCodings(codeSystem, [
{ system: codeSystem.url, code: '1' }, // valid
{ system: codeSystem.url, code: '2' }, // valid
{ system: codeSystem.url, code: 'invalid-code' }, // invalid
{ system: 'incorrect-system', code: '1' }, // invalid
{ system: codeSystem.url, code: undefined }, // invalid
{ system: undefined, code: '1' }, // valid
{ system: undefined, code: 'invalid-code' }, // invalid
{ system: codeSystem.url, code: '2' }, // valid duplicate
]);
expect(result).toMatchObject([
{ system: codeSystem.url, code: '1', display: 'Biopsy of brain' },
{ system: codeSystem.url, code: '2', display: 'Biopsy of head' },
undefined,
undefined,
undefined,
{ system: codeSystem.url, code: '1', display: 'Biopsy of brain' },
undefined,
{ system: codeSystem.url, code: '2', display: 'Biopsy of head' },
]);
});
});