-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexportToNDJson.test.js
More file actions
271 lines (249 loc) · 12.4 KB
/
Copy pathexportToNDJson.test.js
File metadata and controls
271 lines (249 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
const build = require('../../src/server/app');
const {
exportToNDJson,
patientsQueryForType,
getDocuments,
buildSearchParamList
} = require('../../src/util/exportToNDJson');
const QueryBuilder = require('@asymmetrik/fhir-qb');
const { cleanUpDb, createTestResourceWithConnect } = require('../populateTestData');
const testPatient = require('../fixtures/testPatient.json');
const testEncounter = require('../fixtures/testEncounter.json');
const testCondition = require('../fixtures/testCondition.json');
const testValueSet = require('../fixtures/valuesets/example-vs-1.json');
const testServiceRequest = require('../fixtures/testServiceRequest.json');
const app = build();
const fs = require('fs');
const qb = new QueryBuilder({ implementationParameters: { archivedParamPath: '_isArchived' } });
// import queue to close open handles after tests pass
// TODO: investigate why queues are leaving open handles in this file
const queue = require('../../src/resources/exportQueue');
const mockType = ['Patient'];
const expectedFileName = './tmp/123456/Patient.ndjson';
const clientId = '123456';
const mockTypeFilter = 'Patient?maritalStatus:in=http://example.com/example-valueset-1';
const complexMockTypeFilter =
'Patient?maritalStatus:in=http://example.com/example-valueset-1,Encounter?type:in=http://example.com/example-valueset-1,ServiceRequest?code:in=http://example.com/example-valueset-1';
const mockOrTypeFilter = [
'Patient?maritalStatus:in=http://example.com/example-valueset-1',
'Encounter?type:in=http://example.com/example-valueset-1'
];
const expectedFileNameEncounter = './tmp/123456/Encounter.ndjson';
const expectedFileNameServiceRequest = './tmp/123456/ServiceRequest.ndjson';
const typeFilterWOValueSet = 'Procedure?type:in=http';
const typeFilterWithInvalidType = 'Dog?type:in=http://example.com/example-valueset-1';
const expectedFileNameInvalidType = './tmp/123456/Dog.ndjson';
const expectedFileNameWOValueSet = './tmp/123456/Procedure.ndjson';
const axios = require('axios');
jest.mock('axios');
describe('check export logic', () => {
beforeAll(async () => {
await createTestResourceWithConnect(testPatient, 'Patient');
await createTestResourceWithConnect(testEncounter, 'Encounter');
await createTestResourceWithConnect(testCondition, 'Condition');
await createTestResourceWithConnect(testServiceRequest, 'ServiceRequest');
await createTestResourceWithConnect(testValueSet, 'ValueSet');
console.log('created test resource');
});
beforeEach(async () => {
await app.ready();
});
describe('buildSearchParamList', () => {
test('returns record of valid search params for valid resource type', () => {
const results = buildSearchParamList('Encounter');
expect(results).toBeDefined();
});
test('returns empty record of valid search params for invalid resource type', () => {
const results = buildSearchParamList('BiologicallyDerivedProduct');
expect(results).toBeDefined();
});
});
describe('exportToNDJson', () => {
beforeEach(async () => {
fs.rmSync('./tmp/123456', { recursive: true, force: true });
});
test('Expect folder created and export successful when _type parameter is retrieved from request', async () => {
await exportToNDJson({ clientEntry: clientId, types: mockType });
expect(fs.existsSync(expectedFileName)).toBe(true);
});
test('Expect folder created and export successful when _type parameter is not present in request', async () => {
await exportToNDJson({ clientEntry: clientId });
expect(fs.existsSync(expectedFileName)).toBe(true);
});
test('Expect folder created, export successful, and submission endpoint called when bulkSubmitEndpoint is present', async () => {
axios.post.mockResolvedValue({ status: 200 });
await exportToNDJson({ clientEntry: clientId, bulkSubmitEndpoint: 'testEndpoint' });
expect(axios.post).toHaveBeenCalledWith(
'testEndpoint',
{
resourceType: 'Parameters',
parameter: [
{ name: 'manifestUrl', valueString: 'http://localhost:3000/bulkstatus/123456' },
{
name: 'submissionStatus',
valueCoding: {
system: 'http://hl7.org/fhir/uv/bulkdata/ValueSet/submission-status',
code: 'complete'
}
},
{ name: 'submitter', valueIdentifier: { value: 'bulkExportSubmitter' } },
{ name: 'submissionId', valueString: '123456' },
{ name: 'fhirBaseUrl', valueString: 'http://localhost:3000' }
]
},
{ headers: { accept: 'application/fhir+json', 'content-type': 'application/fhir+json' } }
);
expect(fs.existsSync(expectedFileName)).toBe(true);
});
test('Expect folder created and export successful when _typeFilter parameter is retrieved from request', async () => {
await exportToNDJson({ clientEntry: clientId, type: mockType, typeFilter: mockTypeFilter });
expect(fs.existsSync(expectedFileName)).toBe(true);
});
test('Expect folder created and export successful when complex _typeFilter parameter is retrieved from request', async () => {
await exportToNDJson({ clientEntry: clientId, type: mockType, typeFilter: complexMockTypeFilter });
expect(fs.existsSync(expectedFileName)).toBe(true);
expect(fs.existsSync(expectedFileNameEncounter)).toBe(true);
expect(fs.existsSync(expectedFileNameServiceRequest)).toBe(true);
});
test('Expect folder created and export successful when Array _typeFilter parameter is retrieved from request', async () => {
await exportToNDJson({ clientEntry: clientId, type: mockType, typeFilter: mockOrTypeFilter });
expect(fs.existsSync(expectedFileName)).toBe(true);
expect(fs.existsSync(expectedFileNameEncounter)).toBe(true);
});
test('Expect folder created and export to fail when _typeFilter parameter is retrieved from request and contains an invalid param', async () => {
// Note: invalid types are checked in the export service
await exportToNDJson({ clientEntry: clientId, type: mockType, typeFilter: typeFilterWithInvalidType });
expect(fs.existsSync('./tmp/123456')).toBe(true);
expect(fs.existsSync(expectedFileNameInvalidType)).toBe(false);
});
test('Expect export to fail when _typeFilter parameter is retrieved from request but the value set is invalid', async () => {
await exportToNDJson({ clientEntry: clientId, type: mockType, typeFilter: typeFilterWOValueSet });
expect(fs.existsSync(expectedFileNameWOValueSet)).toBe(false);
});
test('Expect folder created and export successful when organizeOutputBy=Patient parameter is retrieved from request', async () => {
await exportToNDJson({ clientEntry: clientId, types: mockType, typeFilter: mockTypeFilter, byPatient: true });
expect(fs.existsSync('./tmp/123456/testPatient.ndjson')).toBe(true);
});
});
describe('patientsQueryForType', () => {
test('Expect patientsQueryForType to succeed for existing resources', async () => {
const query = await patientsQueryForType(['testPatient'], 'Encounter');
expect(query).toEqual({ $or: [{ $or: [{ 'subject.reference': 'Patient/testPatient' }] }] });
});
});
describe('getDocuments', () => {
describe('_typeFilter tests', () => {
test('returns Condition document when _typeFilter=Condition?recorded-date=gt2019-01-03T00:00:00Z', async () => {
const property = {
'recorded-date': 'gt2019-01-03T00:00:00Z'
};
const searchParams = buildSearchParamList('Condition');
const filter = qb.buildSearchQuery({
req: { method: 'GET', query: property, params: {} },
parameterDefinitions: searchParams,
includeArchived: true
});
const docObj = await getDocuments('Condition', [filter.query], undefined, ['testPatient']);
expect(docObj.document.length).toEqual(1);
});
test('returns Condition document when _typeFilter=Condition?recorded-date=gt2019-01-03T00:00:00Z&onset-date=gt2019-01-03T00:00:00Z', async () => {
// test for the "&" operator within the query
const properties = {
'recorded-date': 'gt2019-01-03T00:00:00Z',
'onset-date': 'gt2019-01-03T00:00:00Z'
};
const searchParams = buildSearchParamList('Condition');
const filter = qb.buildSearchQuery({
req: { method: 'GET', query: properties, params: {} },
parameterDefinitions: searchParams,
includeArchived: true
});
const docObj = await getDocuments('Condition', [filter.query], undefined, ['testPatient']);
expect(docObj.document.length).toEqual(1);
});
test('returns no documents when _typeFilter filters out all documents (_typeFilter=Condition?recorded-date=gt2019-01-03T00:00:00Z&onset-date=lt2019-01-03T00:00:00Z', async () => {
const properties = {
'recorded-date': 'gt2019-01-03T00:00:00Z',
'onset-date': 'lt2019-01-03T00:00:00Z'
};
const searchParams = buildSearchParamList('Condition');
const filter = qb.buildSearchQuery({
req: { method: 'GET', query: properties, params: {} },
parameterDefinitions: searchParams,
includeArchived: true
});
const docObj = await getDocuments('Condition', [filter.query], undefined, ['testPatient']);
expect(docObj.document.length).toEqual(0);
});
test('returns Condition document when _typeFilter has "or" condition (_typeFilter=Condition?recorded-date=gt2019-01-03T00:00:00Z,onset-date=lt2019-01-03T00:00:00Z', async () => {
const recordedDateProperty = {
'recorded-date': 'gt2019-01-03T00:00:00Z'
};
const onsetDateTimeProperty = {
'onset-date': 'lt2019-01-03T00:00:00Z'
};
const searchParams = buildSearchParamList('Condition');
const recordedDateFilter = qb.buildSearchQuery({
req: { method: 'GET', query: recordedDateProperty, params: {} },
parameterDefinitions: searchParams,
includeArchived: true
});
const onsetDateTimeFilter = qb.buildSearchQuery({
req: { method: 'GET', query: onsetDateTimeProperty, params: {} },
parameterDefinitions: searchParams,
includeArchived: true
});
const docObj = await getDocuments(
'Condition',
[recordedDateFilter.query, onsetDateTimeFilter.query],
undefined,
['testPatient']
);
expect(docObj.document.length).toEqual(1);
});
});
describe('Patient-based filtering tests', () => {
test('Expect getDocuments to find a resource associated with a patient (Group export)', async () => {
const docObj = await getDocuments('Encounter', undefined, undefined, ['testPatient']);
expect(docObj.document.length).toEqual(1);
});
test('Expect getDocuments to find the encounter resource with no patient association (Patient export)', async () => {
const docObj = await getDocuments('Encounter', undefined, undefined, undefined);
expect(docObj.document.length).toEqual(1);
});
test('Expect getDocuments to return empty results for 0 patient association (empty Group)', async () => {
const docObj = await getDocuments('Encounter', undefined, undefined, []);
expect(docObj.document.length).toEqual(0);
});
});
describe('_elements tests', () => {
test('returns Condition document with only the id, resourceType and subject (mandatory elements for Condition), and the SUBSETTED tag when _elements=Condition.id', async () => {
const docObj = await getDocuments('Condition', undefined, undefined, undefined, ['id']);
expect(docObj.document.length).toEqual(1);
expect(docObj.document[0]).toEqual({
resourceType: 'Condition',
id: 'test-condition',
subject: {
reference: 'Patient/testPatient'
},
meta: {
tag: [
{
code: 'SUBSETTED',
system: 'http://terminology.hl7.org/CodeSystem/v3-ObservationValue'
}
]
}
});
});
});
});
afterAll(async () => {
await cleanUpDb();
});
// Close export queue that is created when processing these tests
// TODO: investigate why queues are leaving open handles in this file
afterEach(async () => {
await queue.close();
});
});