From 2a31155af0f6908c78199f0a8fced9f840702033 Mon Sep 17 00:00:00 2001 From: lizozom Date: Mon, 4 Mar 2024 21:36:34 +0200 Subject: [PATCH 1/6] fix: restore lost PR #28 --- src/controllers/root.ts | 4 ++-- src/helpers/conformance/index.ts | 12 +++++++----- src/helpers/fhirFunctions/translateCode.test.ts | 10 +++++++--- src/index.ts | 6 +++--- src/server.ts | 2 +- src/types/FumeServer.ts | 2 +- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/controllers/root.ts b/src/controllers/root.ts index e1cf201..c5c311b 100644 --- a/src/controllers/root.ts +++ b/src/controllers/root.ts @@ -7,7 +7,7 @@ import type { Request, Response } from 'express'; import config from '../config'; import { getCache } from '../helpers/cache'; -import conformance from '../helpers/conformance'; +import { recacheFromServer } from '../helpers/conformance'; import { v2json } from '../helpers/hl7v2'; import { transform } from '../helpers/jsonataFunctions'; import { getLogger } from '../helpers/logger'; @@ -67,7 +67,7 @@ const evaluate = async (req: Request, res: Response) => { const recache = async (req: Request, res: Response) => { try { - const recacheSuccess = await conformance.recacheFromServer(); + const recacheSuccess = await recacheFromServer(); if (recacheSuccess) { const { tables, compiledMappings } = getCache(); diff --git a/src/helpers/conformance/index.ts b/src/helpers/conformance/index.ts index 71844bf..0dfc2a1 100644 --- a/src/helpers/conformance/index.ts +++ b/src/helpers/conformance/index.ts @@ -1,12 +1,14 @@ import { getStructureDefinition, getTable } from './conformance'; -import { getFhirPackageIndex, loadFhirPackageIndex } from './loadFhirPackageIndex'; +import { getFhirPackageIndex, IFhirPackageIndex, loadFhirPackageIndex } from './loadFhirPackageIndex'; import { loadPackage, loadPackages } from './loadPackages'; -import { recacheFromServer } from './recacheFromServer'; +import { getAliasResource, recacheFromServer } from './recacheFromServer'; -export default { - getTable, - getStructureDefinition, +export { + getAliasResource, getFhirPackageIndex, + getStructureDefinition, + getTable, + IFhirPackageIndex, loadFhirPackageIndex, loadPackage, loadPackages, diff --git a/src/helpers/fhirFunctions/translateCode.test.ts b/src/helpers/fhirFunctions/translateCode.test.ts index 4cc3ce2..836a772 100644 --- a/src/helpers/fhirFunctions/translateCode.test.ts +++ b/src/helpers/fhirFunctions/translateCode.test.ts @@ -1,9 +1,13 @@ import { test } from '@jest/globals'; import { initCache } from '../cache'; -import conformance from '../conformance'; +import { getTable } from '../conformance'; import { translateCode } from './translateCode'; +jest.mock('../conformance', () => ({ + getTable: jest.fn() +})); + jest.mock('../logger', () => ({ getLogger: jest.fn().mockReturnValue({ info: jest.fn(), @@ -16,12 +20,12 @@ describe('translateCode', () => { beforeEach(() => { initCache(); - mockGetTable = jest.spyOn(conformance, 'getTable'); + mockGetTable = getTable as jest.Mock; mockGetTable.mockResolvedValue({ tableId: { input: [{ code: 1 }] } }); }); afterEach(() => { - mockGetTable.mockRestore(); + (getTable as jest.Mock).mockRestore(); }); test('returns undefined on error', async () => { diff --git a/src/index.ts b/src/index.ts index 8dd4cdd..e665972 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ */ import config from './config'; -import { getAliasResource } from './helpers/conformance/recacheFromServer'; +import { getAliasResource } from './helpers/conformance'; import { v2json } from './helpers/hl7v2'; import expressions from './helpers/jsonataExpression'; import { selectKeys } from './helpers/objectFunctions'; @@ -32,6 +32,7 @@ export { FumeConfigSchema } from './serverConfigSchema'; export const fumeUtils = { expressions, duplicate, + getAliasResource, substringBefore, substringAfter, selectKeys, @@ -43,6 +44,5 @@ export const fumeUtils = { getFhirVersionMinor: config.getFhirVersionMinor, toJsonataString: parser.toJsonataString, getSnapshot: parser.getSnapshot, - v2json, - getAliasResource + v2json }; diff --git a/src/server.ts b/src/server.ts index 2785c4f..0bc4c1d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,7 +4,7 @@ import type { Server } from 'http'; import config from './config'; import { getCache, IAppCacheKeys, initCache, InitCacheConfig } from './helpers/cache'; -import conformance from './helpers/conformance'; +import * as conformance from './helpers/conformance'; import { FhirClient, setFhirClient } from './helpers/fhirServer'; import { transform } from './helpers/jsonataFunctions'; import { getLogger, setLogger } from './helpers/logger'; diff --git a/src/types/FumeServer.ts b/src/types/FumeServer.ts index 28991af..9db4655 100644 --- a/src/types/FumeServer.ts +++ b/src/types/FumeServer.ts @@ -1,7 +1,7 @@ import { Application } from 'express'; import { IAppCache, IAppCacheKeys } from '../helpers/cache/cacheTypes'; -import { IFhirPackageIndex } from '../helpers/conformance/loadFhirPackageIndex'; +import { IFhirPackageIndex } from '../helpers/conformance'; import { ICache } from './Cache'; import { IFhirClient } from './FhirClient'; import { ILogger } from './Logger'; From 43063813998967d10e3fc5d898c6081d64249171 Mon Sep 17 00:00:00 2001 From: lizozom Date: Mon, 4 Mar 2024 21:43:00 +0200 Subject: [PATCH 2/6] fix: rebuild package lock --- package-lock.json | 4 ++-- src/helpers/fhirFunctions/translateCode.ts | 4 ++-- src/helpers/jsonataFunctions/getStructureDefinition.ts | 4 ++-- src/helpers/jsonataFunctions/transform.ts | 2 +- src/helpers/runtime/castToFhir.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e2031e..e6bc032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fume-fhir-converter", - "version": "2.0.5", + "version": "2.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fume-fhir-converter", - "version": "2.0.5", + "version": "2.0.8", "license": "AGPL-3.0", "dependencies": { "axios": "^1.1.3", diff --git a/src/helpers/fhirFunctions/translateCode.ts b/src/helpers/fhirFunctions/translateCode.ts index fbc297e..d107fb8 100644 --- a/src/helpers/fhirFunctions/translateCode.ts +++ b/src/helpers/fhirFunctions/translateCode.ts @@ -1,6 +1,6 @@ import { getCache } from '../cache'; -import conformance from '../conformance'; +import { getTable } from '../conformance'; import expressions from '../jsonataExpression'; import { getLogger } from '../logger'; @@ -11,7 +11,7 @@ export const translateCode = async (input: string, tableId: string) => { let map = tables.get(tableId); if (map === undefined) { getLogger().info(`Table ${tableId} not cached, trying to fetch from server...`); - map = (await conformance.getTable(tableId))[tableId]; + map = (await getTable(tableId))[tableId]; tables.set(tableId, map); }; diff --git a/src/helpers/jsonataFunctions/getStructureDefinition.ts b/src/helpers/jsonataFunctions/getStructureDefinition.ts index 9970b87..5e0afed 100644 --- a/src/helpers/jsonataFunctions/getStructureDefinition.ts +++ b/src/helpers/jsonataFunctions/getStructureDefinition.ts @@ -1,5 +1,5 @@ import config from '../../config'; -import conformance from '../conformance'; +import { getFhirPackageIndex } from '../conformance'; import fhirFuncs from '../fhirFunctions'; import { getLogger } from '../logger'; import thrower from '../thrower'; @@ -8,7 +8,7 @@ const getStructureDefinitionPath = (definitionId: string): any => { // fork: os const serverConfig = config.getServerConfig(); const fhirVersionMinor = fhirFuncs.fhirVersionToMinor(serverConfig.FHIR_VERSION); - const fhirPackageIndex = conformance.getFhirPackageIndex(); + const fhirPackageIndex = getFhirPackageIndex(); const cached = fhirPackageIndex[fhirVersionMinor]; const indexed = cached.structureDefinitions.byId[definitionId] ?? cached.structureDefinitions.byUrl[definitionId] ?? diff --git a/src/helpers/jsonataFunctions/transform.ts b/src/helpers/jsonataFunctions/transform.ts index ef4329a..2e7a555 100644 --- a/src/helpers/jsonataFunctions/transform.ts +++ b/src/helpers/jsonataFunctions/transform.ts @@ -10,7 +10,7 @@ import jsonata from 'jsonata'; import { IAppBinding } from '../../types'; import { getCache } from '../cache'; -import conformance from '../conformance'; +import * as conformance from '../conformance'; import fhirFuncs from '../fhirFunctions'; import * as v2 from '../hl7v2'; import { getLogger } from '../logger'; diff --git a/src/helpers/runtime/castToFhir.ts b/src/helpers/runtime/castToFhir.ts index f9a0f8a..7da29c5 100644 --- a/src/helpers/runtime/castToFhir.ts +++ b/src/helpers/runtime/castToFhir.ts @@ -6,7 +6,7 @@ import _ from 'lodash'; import uuidByString from 'uuid-by-string'; -import conformance from '../conformance'; +import { getStructureDefinition } from '../conformance'; import thrower from '../thrower'; export interface CastToFhirOptions { @@ -29,7 +29,7 @@ const getPrimitiveParser = async (typeName: string): Promise Date: Wed, 6 Mar 2024 15:57:44 +0200 Subject: [PATCH 3/6] test: cache test --- src/helpers/cache/cache.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/helpers/cache/cache.test.ts b/src/helpers/cache/cache.test.ts index 97db03b..19abc87 100644 --- a/src/helpers/cache/cache.test.ts +++ b/src/helpers/cache/cache.test.ts @@ -1,6 +1,9 @@ import { test } from '@jest/globals'; import { getCache, initCache } from './cache'; +import { SimpleCache } from './simpleCache'; + +class TestCache extends SimpleCache {} describe('SimpleCache', () => { test('getCache fails if not init', async () => { @@ -15,4 +18,16 @@ describe('SimpleCache', () => { getCache(); }).not.toThrow(); }); + + test('initCache with override class', async () => { + initCache({ + aliases: { + cacheClass: TestCache, + cacheClassOptions: {} + } + }); + const { aliases, mappings } = getCache(); + expect(aliases).toBeInstanceOf(TestCache); + expect(mappings).toBeInstanceOf(SimpleCache); + }); }); From 6c94daab42164e4717a077db9add3076531b9bd0 Mon Sep 17 00:00:00 2001 From: lizozom Date: Wed, 6 Mar 2024 19:34:54 +0200 Subject: [PATCH 4/6] test: add tests for fhir client (stateless, axios, config) --- src/helpers/fhirServer/client.test.ts | 167 ++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/helpers/fhirServer/client.test.ts diff --git a/src/helpers/fhirServer/client.test.ts b/src/helpers/fhirServer/client.test.ts new file mode 100644 index 0000000..cc61e0e --- /dev/null +++ b/src/helpers/fhirServer/client.test.ts @@ -0,0 +1,167 @@ + +import { test } from '@jest/globals'; +import mockAxios from 'jest-mock-axios'; + +import config from '../../config'; +import { FhirClient } from './client'; + +const mockConfig = { + FHIR_SERVER_BASE: 'test.com', + FHIR_SERVER_TIMEOUT: 1000, + SERVER_PORT: 111, + FHIR_SERVER_AUTH_TYPE: '', + FHIR_SERVER_UN: '', + FHIR_SERVER_PW: '', + SERVER_STATELESS: false, + FHIR_VERSION: '4.0.1', + SEARCH_BUNDLE_PAGE_SIZE: 200, + FHIR_PACKAGES: '', + EXCLUDE_FHIR_PACKAGES: '' +}; + +describe('FhirClient', () => { + let mockAxiosInstance: jest.Mocked; + + afterEach(() => { + jest.resetAllMocks(); + mockAxios.reset(); + }); + + beforeEach(() => { + mockAxiosInstance = { + get: jest.fn(), + post: jest.fn(), + put: jest.fn(), + delete: jest.fn() + + } as any; + mockAxios.create.mockReturnValue(mockAxiosInstance); + }); + + test('constructor uses config, no auth, stateless = false', async () => { + config.setServerConfig(mockConfig); + /* eslint-disable no-new */ + new FhirClient(); + + expect(mockAxios.create).toHaveBeenCalledWith({ + baseURL: mockConfig.FHIR_SERVER_BASE, + timeout: mockConfig.FHIR_SERVER_TIMEOUT, + headers: { + Accept: 'application/fhir+json;fhirVersion=4.0', + 'Content-Type': 'application/fhir+json;fhirVersion=4.0', + Authorization: undefined + } + }); + }); + + test('constructor uses config, with BASIC auth, stateless = false', async () => { + config.setServerConfig({ + ...mockConfig, + FHIR_SERVER_AUTH_TYPE: 'BASIC', + FHIR_SERVER_UN: 'user', + FHIR_SERVER_PW: 'pw' + }); + /* eslint-disable no-new */ + new FhirClient(); + + expect(mockAxios.create).toHaveBeenCalledWith({ + baseURL: mockConfig.FHIR_SERVER_BASE, + timeout: mockConfig.FHIR_SERVER_TIMEOUT, + headers: { + Accept: 'application/fhir+json;fhirVersion=4.0', + 'Content-Type': 'application/fhir+json;fhirVersion=4.0', + Authorization: 'Basic dXNlcjpwdw==' + } + }); + }); + + test('constructor uses config, no auth, stateless = false', async () => { + config.setServerConfig({ + ...mockConfig, + SERVER_STATELESS: true + }); + + /* eslint-disable no-new */ + new FhirClient(); + + expect(mockAxios.create).not.toHaveBeenCalled(); + }); + + test('read returns empty in stateless mode', async () => { + config.setServerConfig({ + ...mockConfig, + SERVER_STATELESS: true + }); + const client = new FhirClient(); + const url = 'test'; + const res = await client.read(url); + expect(res).toBeUndefined(); + expect(mockAxios.get).not.toHaveBeenCalled(); + }); + + test('read returns response data', async () => { + config.setServerConfig(mockConfig); + const client = new FhirClient(); + const url = 'test-url'; + mockAxiosInstance.get.mockResolvedValue({ data: 'test' }); + const response = await client.read(url); + expect(response).toBe('test'); + expect(mockAxiosInstance.get).toHaveBeenCalledWith(url); + }); + + test('search returns empty in stateless mode', async () => { + config.setServerConfig({ + ...mockConfig, + SERVER_STATELESS: true + }); + const client = new FhirClient(); + const url = 'test'; + const res = await client.search(url); + expect(res).toBeUndefined(); + expect(mockAxios.get).not.toHaveBeenCalled(); + }); + + test('search returns response data', async () => { + config.setServerConfig({ + ...mockConfig, + SEARCH_BUNDLE_PAGE_SIZE: 45 + }); + const client = new FhirClient(); + const url = 'test-url'; + mockAxiosInstance.get.mockResolvedValue({ data: 'test' }); + const response = await client.search(url, { test: 'test-param' }); + expect(response).toBe('test'); + + expect(mockAxiosInstance.get).toHaveBeenCalledWith('/test-url', { params: { _count: 45, test: 'test-param' } }); + }); + + test('create throws error in community', async () => { + config.setServerConfig({ + ...mockConfig, + SERVER_STATELESS: true + }); + const client = new FhirClient(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + expect(client.create({}, 'test')).rejects.toThrow('Method not implemented.'); + }); + + test('update throws error in community', async () => { + config.setServerConfig({ + ...mockConfig, + SERVER_STATELESS: true + }); + const client = new FhirClient(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + expect(client.update('test', 'test', {})).rejects.toThrow('Method not implemented.'); + }); + + test('simpleDelete throws error in community', async () => { + config.setServerConfig({ + ...mockConfig, + SERVER_STATELESS: true + }); + const client = new FhirClient(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + expect(client.simpleDelete('test', 'test')).rejects.toThrow('Method not implemented.'); + }); +}); From 6a6d5ec161b12924ba576f2e115a745db4fbe8c0 Mon Sep 17 00:00:00 2001 From: lizozom Date: Wed, 6 Mar 2024 19:43:41 +0200 Subject: [PATCH 5/6] test: config (stateless, fhir base, defaults) --- src/config.test.ts | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/config.ts | 10 ++++--- 2 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/config.test.ts diff --git a/src/config.test.ts b/src/config.test.ts new file mode 100644 index 0000000..d9302d0 --- /dev/null +++ b/src/config.test.ts @@ -0,0 +1,65 @@ +/** + * © Copyright Outburn Ltd. 2022-2023 All Rights Reserved + * Project name: FUME + */ + +import { test } from '@jest/globals'; + +import config from './config'; + +describe('setServerConfig', () => { + test('Uses defaults', async () => { + config.setServerConfig({}); + expect(config.getServerConfig()).toEqual({ + EXCLUDE_FHIR_PACKAGES: '', + FHIR_PACKAGES: 'il.core.fhir.r4@0.11.0,hl7.fhir.us.core@6.0.0,fhir.outburn.co.il@0.0.1,laniado.test.fhir.r4', + FHIR_SERVER_AUTH_TYPE: 'NONE', + FHIR_SERVER_BASE: '', + FHIR_SERVER_PW: '', + FHIR_SERVER_TIMEOUT: 10000, + FHIR_SERVER_UN: '', + FHIR_VERSION: '4.0.1', + SEARCH_BUNDLE_PAGE_SIZE: 20, + SERVER_PORT: 42420, + SERVER_STATELESS: true + }); + }); + + test('Uses defaults, with FHIR_SERVER_BASE, sets stateless to false', async () => { + config.setServerConfig({ + FHIR_SERVER_BASE: 'http://hapi-fhir.outburn.co.il/fhir-test ' + }); + expect(config.getServerConfig()).toEqual({ + EXCLUDE_FHIR_PACKAGES: '', + FHIR_PACKAGES: 'il.core.fhir.r4@0.11.0,hl7.fhir.us.core@6.0.0,fhir.outburn.co.il@0.0.1,laniado.test.fhir.r4', + FHIR_SERVER_AUTH_TYPE: 'NONE', + FHIR_SERVER_BASE: 'http://hapi-fhir.outburn.co.il/fhir-test', + FHIR_SERVER_PW: '', + FHIR_SERVER_TIMEOUT: 10000, + FHIR_SERVER_UN: '', + FHIR_VERSION: '4.0.1', + SEARCH_BUNDLE_PAGE_SIZE: 20, + SERVER_PORT: 42420, + SERVER_STATELESS: false + }); + }); + + test('Uses defaults, without FHIR_SERVER_BASE, sets stateless to true', async () => { + config.setServerConfig({ + SERVER_STATELESS: false + }); + expect(config.getServerConfig()).toEqual({ + EXCLUDE_FHIR_PACKAGES: '', + FHIR_PACKAGES: 'il.core.fhir.r4@0.11.0,hl7.fhir.us.core@6.0.0,fhir.outburn.co.il@0.0.1,laniado.test.fhir.r4', + FHIR_SERVER_AUTH_TYPE: 'NONE', + FHIR_SERVER_BASE: '', + FHIR_SERVER_PW: '', + FHIR_SERVER_TIMEOUT: 10000, + FHIR_SERVER_UN: '', + FHIR_VERSION: '4.0.1', + SEARCH_BUNDLE_PAGE_SIZE: 20, + SERVER_PORT: 42420, + SERVER_STATELESS: true + }); + }); +}); diff --git a/src/config.ts b/src/config.ts index e56151e..dc030cb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,13 +10,15 @@ import type { IAppBinding, IConfig } from './types'; const additionalBindings: Record = {}; // additional functions to bind when running transformations let serverConfig: IConfig = { ...defaultConfig }; -const setServerConfig = (config: ConfigType) => { - let fhirServerBase: string = config.FHIR_SERVER_BASE; - let isStatelessMode: boolean = config.SERVER_STATELESS; +const setServerConfig = (config: Partial) => { + let fhirServerBase: string | undefined = config.FHIR_SERVER_BASE?.trim(); + let isStatelessMode: boolean | undefined = config.SERVER_STATELESS; - if (!fhirServerBase || fhirServerBase.trim() === '' || isStatelessMode) { + if (!fhirServerBase || isStatelessMode) { fhirServerBase = ''; isStatelessMode = true; + } else { + isStatelessMode = false; }; serverConfig = { ...defaultConfig, From 1a62ae83c78f6b5f8c74f9e7ce902dad081b97b7 Mon Sep 17 00:00:00 2001 From: lizozom Date: Wed, 6 Mar 2024 20:02:18 +0200 Subject: [PATCH 6/6] test: fix config --- src/config.test.ts | 26 +++++++++++++++++--------- src/config.ts | 4 ++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/config.test.ts b/src/config.test.ts index d9302d0..d5216a6 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -7,20 +7,28 @@ import { test } from '@jest/globals'; import config from './config'; +const serverDefaults = { + EXCLUDE_FHIR_PACKAGES: '', + FHIR_PACKAGES: 'il.core.fhir.r4@0.11.0,hl7.fhir.us.core@6.0.0,fhir.outburn.co.il@0.0.1,laniado.test.fhir.r4', + FHIR_SERVER_AUTH_TYPE: 'NONE', + FHIR_SERVER_BASE: 'http://hapi-fhir.outburn.co.il/fhir', + FHIR_SERVER_PW: '', + FHIR_SERVER_TIMEOUT: 10000, + FHIR_SERVER_UN: '', + FHIR_VERSION: '4.0.1', + SEARCH_BUNDLE_PAGE_SIZE: 20, + SERVER_PORT: 42420, + SERVER_STATELESS: false +}; + +jest.mock('./serverConfig', () => (serverDefaults)); + describe('setServerConfig', () => { test('Uses defaults', async () => { config.setServerConfig({}); expect(config.getServerConfig()).toEqual({ - EXCLUDE_FHIR_PACKAGES: '', - FHIR_PACKAGES: 'il.core.fhir.r4@0.11.0,hl7.fhir.us.core@6.0.0,fhir.outburn.co.il@0.0.1,laniado.test.fhir.r4', - FHIR_SERVER_AUTH_TYPE: 'NONE', + ...serverDefaults, FHIR_SERVER_BASE: '', - FHIR_SERVER_PW: '', - FHIR_SERVER_TIMEOUT: 10000, - FHIR_SERVER_UN: '', - FHIR_VERSION: '4.0.1', - SEARCH_BUNDLE_PAGE_SIZE: 20, - SERVER_PORT: 42420, SERVER_STATELESS: true }); }); diff --git a/src/config.ts b/src/config.ts index dc030cb..a0a898d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -11,10 +11,10 @@ const additionalBindings: Record = {}; // additional functi let serverConfig: IConfig = { ...defaultConfig }; const setServerConfig = (config: Partial) => { - let fhirServerBase: string | undefined = config.FHIR_SERVER_BASE?.trim(); + let fhirServerBase: string | undefined = config.FHIR_SERVER_BASE ? config.FHIR_SERVER_BASE.trim() : undefined; let isStatelessMode: boolean | undefined = config.SERVER_STATELESS; - if (!fhirServerBase || isStatelessMode) { + if (!fhirServerBase || fhirServerBase === '' || isStatelessMode) { fhirServerBase = ''; isStatelessMode = true; } else {