Skip to content

Commit c533b0d

Browse files
committed
Change findFHIRResource to use getFHIRResources under the hood. Cover with tests
1 parent c272ac9 commit c533b0d

File tree

5 files changed

+102
-90
lines changed

5 files changed

+102
-90
lines changed

src/services/fhir.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AxiosRequestConfig } from 'axios';
22
import { AidboxReference, AidboxResource, ValueSet, Bundle, BundleEntry, id } from 'shared/src/contrib/aidbox';
33

4-
import { isFailure, RemoteDataResult, success } from '../libs/remoteData';
4+
import { isFailure, RemoteDataResult, success, failure } from '../libs/remoteData';
55
import { buildQueryParams } from './instance';
66
import { SearchParams } from './search';
77
import { service } from './service';
@@ -190,7 +190,10 @@ export async function getAllFHIRResources<R extends AidboxResource>(
190190
return response;
191191
}
192192

193-
resultBundle = { ...response.data, entry: [...resultBundle.entry!, ...response.data.entry] };
193+
resultBundle = {
194+
...response.data,
195+
entry: [...resultBundle.entry!, ...response.data.entry],
196+
};
194197
}
195198

196199
return success(resultBundle);
@@ -213,30 +216,24 @@ export async function findFHIRResource<R extends AidboxResource>(
213216
params: SearchParams,
214217
extraPath?: string
215218
): Promise<RemoteDataResult<WithId<R>>> {
216-
return await service(find(resourceType, params, extraPath));
217-
}
219+
const response = await getFHIRResources<R>(resourceType, params, extraPath);
218220

219-
export function find<R extends AidboxResource>(
220-
resourceType: R['resourceType'],
221-
params: SearchParams,
222-
extraPath?: string
223-
): AxiosRequestConfig {
224-
return {
225-
method: 'GET',
226-
url: extraPath ? `/${resourceType}/${extraPath}` : `/${resourceType}`,
227-
params: { ...params, ...getInactiveSearchParam(resourceType) },
228-
transformResponse: (resp: string) => {
229-
const data: Bundle<R> = JSON.parse(resp);
230-
const resources = data.entry!;
231-
if (resources.length === 1) {
232-
return resources[0].resource!;
233-
} else if (resources.length === 0) {
234-
throw new Error('No resources found');
235-
} else {
236-
throw new Error('Too many resources found');
237-
}
238-
},
239-
};
221+
if (isFailure(response)) {
222+
return response;
223+
}
224+
225+
const resources = extractBundleResources(response.data)[resourceType] as WithId<R>[];
226+
227+
if (resources.length === 1) {
228+
return success(resources[0]);
229+
} else if (resources.length === 0) {
230+
return failure({ error_description: 'No resources found', error: 'no_resources_found' });
231+
} else {
232+
return failure({
233+
error_description: 'Too many resources found',
234+
error: 'too_many_resources_found',
235+
});
236+
}
240237
}
241238

242239
export async function saveFHIRResource<R extends AidboxResource>(resource: R): Promise<RemoteDataResult<WithId<R>>> {

src/utils/error.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ export function extractErrorDescription(error: any) {
6767
}
6868

6969
if (isOperationOutcome(error)) {
70-
if (error.issue[0].details?.text) {
71-
return error.issue[0].details?.text;
70+
if (error.issue?.[0]?.details?.text) {
71+
return error.issue[0].details.text;
7272
}
7373

74-
if (error.issue[0].diagnostics) {
74+
if (error.issue?.[0]?.diagnostics) {
7575
return error.issue[0].diagnostics;
7676
}
7777
}

tests/services/fhir.spec.ts

Lines changed: 65 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { AxiosTransformer } from 'axios';
21
import { Bundle, Patient, Practitioner } from 'shared/src/contrib/aidbox';
32

4-
import { success } from '../../src/libs/remoteData';
3+
import { failure, success } from '../../src/libs/remoteData';
54
import {
65
create,
76
createFHIRResource,
87
get,
98
getFHIRResource,
109
list,
1110
getFHIRResources,
12-
find,
1311
findFHIRResource,
1412
update,
1513
updateFHIRResource,
@@ -36,12 +34,13 @@ import {
3634
import { service } from '../../src/services/service';
3735

3836
jest.mock('../../src/services/service', () => {
39-
return { service: jest.fn(() => Promise.resolve(success('data'))) };
37+
return { service: jest.fn() };
4038
});
4139

4240
describe('Service `fhir`', () => {
4341
beforeEach(() => {
4442
jest.clearAllMocks();
43+
(<jest.Mock>service).mockImplementation(() => Promise.resolve(success('data')));
4544
});
4645

4746
describe('method `create`', () => {
@@ -361,74 +360,75 @@ describe('Service `fhir`', () => {
361360
});
362361
});
363362

364-
describe('method `find`', () => {
365-
test('has correct behavior', async () => {
363+
describe('method `findFHIRResource`', () => {
364+
test('returns failure when nothing found', async () => {
366365
const params = { id: 1 };
367366
const resourceType = 'Patient';
368367

369-
const axiosRequestConfig = find(resourceType, params);
370-
const transformResponse = axiosRequestConfig.transformResponse as AxiosTransformer;
368+
(<jest.Mock>service).mockImplementation(() => Promise.resolve(success({ entry: [] })));
371369

372-
expect(axiosRequestConfig).toEqual(
373-
expect.objectContaining({
374-
method: 'GET',
375-
url: '/' + resourceType,
376-
params: {
377-
'active:not': [false],
378-
id: 1,
379-
},
370+
const response = await findFHIRResource(resourceType, params);
371+
expect(service).toHaveBeenLastCalledWith({
372+
method: 'GET',
373+
url: `/${resourceType}`,
374+
params: { ...params, 'active:not': [false] },
375+
});
376+
expect(response).toEqual(
377+
failure({
378+
error_description: 'No resources found',
379+
error: 'no_resources_found',
380380
})
381381
);
382-
383-
expect(() => {
384-
const response = '';
385-
transformResponse(response);
386-
}).toThrow();
387-
388-
expect(() => {
389-
const response = '{"entry":[]}';
390-
transformResponse(response);
391-
}).toThrow();
392-
393-
const response = '{"entry":[{"resource": "data"}]}';
394-
const transformed = transformResponse(response);
395-
396-
expect(transformed).toBe('data');
397-
398-
expect(() => {
399-
const response = '{"entry":[{"resource": "a"}, {"resource": "b"}]}';
400-
transformResponse(response);
401-
}).toThrow();
402382
});
403383

404-
test('receive extra path argument', async () => {
405-
const params = { id: 1 };
384+
test('returns failure when multiple resources found', async () => {
385+
const id = 'patient-id';
386+
const params = { _id: id };
406387
const resourceType = 'Patient';
407-
const extraPath = 'extraPath';
388+
const resource = { resourceType, id };
389+
(<jest.Mock>service).mockImplementation(() =>
390+
Promise.resolve(success({ entry: [{ resource }, { resource }] }))
391+
);
408392

409-
expect(find(resourceType, params, extraPath)).toEqual(
410-
expect.objectContaining({
411-
method: 'GET',
412-
url: '/' + resourceType + '/' + extraPath,
413-
params: {
414-
'active:not': [false],
415-
id: 1,
416-
},
393+
const response = await findFHIRResource(resourceType, params);
394+
expect(service).toHaveBeenLastCalledWith({
395+
method: 'GET',
396+
url: `/${resourceType}`,
397+
params: { ...params, 'active:not': [false] },
398+
});
399+
expect(response).toEqual(
400+
failure({
401+
error_description: 'Too many resources found',
402+
error: 'too_many_resources_found',
417403
})
418404
);
419405
});
420-
});
421406

422-
describe('method `findFHIRResource`', () => {
423-
test('has correct behavior', async () => {
424-
const params = { id: 1 };
407+
test('returns success when exactly one resource found', async () => {
408+
const id = 'patient-id';
409+
const params = { _id: id };
425410
const resourceType = 'Patient';
411+
const resource = { resourceType, id };
412+
413+
(<jest.Mock>service).mockImplementation(() =>
414+
Promise.resolve(
415+
success({
416+
entry: [
417+
{
418+
resource,
419+
},
420+
],
421+
})
422+
)
423+
);
426424

427-
await findFHIRResource(resourceType, params);
428-
const restFindResult = find(resourceType, params);
429-
delete restFindResult.transformResponse;
430-
431-
expect(service).toHaveBeenLastCalledWith(expect.objectContaining(restFindResult));
425+
const response = await findFHIRResource(resourceType, params);
426+
expect(service).toHaveBeenLastCalledWith({
427+
method: 'GET',
428+
url: `/${resourceType}`,
429+
params: { ...params, 'active:not': [false] },
430+
});
431+
expect(response).toEqual(success(resource));
432432
});
433433

434434
test('receive extra path argument', async () => {
@@ -438,10 +438,11 @@ describe('Service `fhir`', () => {
438438

439439
await findFHIRResource(resourceType, params, extraPath);
440440

441-
const restFindResult = find(resourceType, params, extraPath);
442-
delete restFindResult.transformResponse;
443-
444-
expect(service).toHaveBeenLastCalledWith(expect.objectContaining(restFindResult));
441+
expect(service).toHaveBeenLastCalledWith({
442+
method: 'GET',
443+
url: `/${resourceType}/${extraPath}`,
444+
params: { ...params, 'active:not': [false] },
445+
});
445446
});
446447
});
447448

@@ -627,7 +628,10 @@ describe('Service `fhir`', () => {
627628

628629
describe('method `extractBundleResources`', () => {
629630
test("extract empty object when there's not entry property", () => {
630-
const bundle: Bundle<Patient | Practitioner> = { resourceType: 'Bundle', type: 'searchset' };
631+
const bundle: Bundle<Patient | Practitioner> = {
632+
resourceType: 'Bundle',
633+
type: 'searchset',
634+
};
631635

632636
expect(extractBundleResources(bundle).Practitioner).toEqual([]);
633637
expect(extractBundleResources(bundle).Patient).toEqual([]);

tests/utils/error.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('Util `tests`', () => {
8383
expect(extractErrorDescription(error)).toEqual(result);
8484
});
8585

86-
test('OperationOutcome', () => {
86+
test('OperationOutcome details', () => {
8787
const error = {
8888
resourceType: 'OperationOutcome',
8989
issue: [{ code: 'code', details: { text: 'Description' } }],
@@ -92,6 +92,16 @@ describe('Util `tests`', () => {
9292

9393
expect(extractErrorDescription(error)).toEqual(result);
9494
});
95+
96+
test('OperationOutcome diagnostics', () => {
97+
const error = {
98+
resourceType: 'OperationOutcome',
99+
issue: [{ code: 'code', diagnostics: 'Description' }],
100+
};
101+
const result = 'Description';
102+
103+
expect(extractErrorDescription(error)).toEqual(result);
104+
});
95105
});
96106

97107
describe('method `formatError` without config', () => {

typings/contrib/aidbox.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,5 @@ export interface OperationOutcomeIssue {
162162
details?: {
163163
text?: string;
164164
};
165+
diagnostics?: text;
165166
}

0 commit comments

Comments
 (0)