Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
feat: add Batch support (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssvegaraju committed Apr 6, 2022
1 parent 1108326 commit 837aff6
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 87 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"cors": "^2.8.5",
"errorhandler": "^1.5.1",
"express": "^4.17.1",
"fhir-works-on-aws-interface": "^12.0.0",
"fhir-works-on-aws-interface": "^12.1.0",
"flat": "^5.0.0",
"http-errors": "^1.8.0",
"lodash": "^4.17.15",
Expand Down
133 changes: 73 additions & 60 deletions src/router/__mocks__/dynamoDbBundleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,80 @@ import {
TransactionRequest,
} from 'fhir-works-on-aws-interface';

const bundleEntryResponses: BatchReadWriteResponse[] = [
{
id: '8cafa46d-08b4-4ee4-b51b-803e20ae8126',
vid: '3',
operation: 'update',
lastModified: '2020-04-23T21:19:35.592Z',
resourceType: 'Patient',
resource: {},
},
{
id: '7c7cf4ca-4ba7-4326-b0dd-f3275b735827',
vid: '1',
operation: 'create',
lastModified: '2020-04-23T21:19:35.592Z',
resourceType: 'Patient',
resource: {},
},
{
id: '47135b80-b721-430b-9d4b-1557edc64947',
vid: '1',
operation: 'read',
lastModified: '2020-04-10T20:41:39.912Z',
resource: {
active: true,
resourceType: 'Patient',
birthDate: '1995-09-24',
meta: {
lastUpdated: '2020-04-10T20:41:39.912Z',
versionId: '1',
},
managingOrganization: {
reference: 'Organization/2.16.840.1.113883.19.5',
display: 'Good Health Clinic',
},
text: {
div: '<div xmlns="http://www.w3.org/1999/xhtml"><p></p></div>',
status: 'generated',
},
id: '47135b80-b721-430b-9d4b-1557edc64947',
name: [
{
family: 'Langard',
given: ['Abby'],
},
],
gender: 'female',
},
resourceType: 'Patient',
},
{
id: 'bce8411e-c15e-448c-95dd-69155a837405',
vid: '1',
operation: 'delete',
lastModified: '2020-04-23T21:19:35.593Z',
resource: {},
resourceType: 'Patient',
},
];

const DynamoDbBundleService: Bundle = class {
static batch(request: BatchRequest): Promise<BundleResponse> {
throw new Error('Method not implemented.');
static async batch(request: BatchRequest): Promise<BundleResponse> {
if (request.requests.length === 0) {
return {
success: true,
message: 'No requests to process',
batchReadWriteResponses: [],
};
}

return {
success: true,
message: 'successfully completed batch',
batchReadWriteResponses: bundleEntryResponses,
};
}

static async transaction(request: TransactionRequest): Promise<BundleResponse> {
Expand All @@ -22,64 +93,6 @@ const DynamoDbBundleService: Bundle = class {
};
}

const bundleEntryResponses: BatchReadWriteResponse[] = [
{
id: '8cafa46d-08b4-4ee4-b51b-803e20ae8126',
vid: '3',
operation: 'update',
lastModified: '2020-04-23T21:19:35.592Z',
resourceType: 'Patient',
resource: {},
},
{
id: '7c7cf4ca-4ba7-4326-b0dd-f3275b735827',
vid: '1',
operation: 'create',
lastModified: '2020-04-23T21:19:35.592Z',
resourceType: 'Patient',
resource: {},
},
{
id: '47135b80-b721-430b-9d4b-1557edc64947',
vid: '1',
operation: 'read',
lastModified: '2020-04-10T20:41:39.912Z',
resource: {
active: true,
resourceType: 'Patient',
birthDate: '1995-09-24',
meta: {
lastUpdated: '2020-04-10T20:41:39.912Z',
versionId: '1',
},
managingOrganization: {
reference: 'Organization/2.16.840.1.113883.19.5',
display: 'Good Health Clinic',
},
text: {
div: '<div xmlns="http://www.w3.org/1999/xhtml"><p></p></div>',
status: 'generated',
},
id: '47135b80-b721-430b-9d4b-1557edc64947',
name: [
{
family: 'Langard',
given: ['Abby'],
},
],
gender: 'female',
},
resourceType: 'Patient',
},
{
id: 'bce8411e-c15e-448c-95dd-69155a837405',
vid: '1',
operation: 'delete',
lastModified: '2020-04-23T21:19:35.593Z',
resource: {},
resourceType: 'Patient',
},
];
return {
success: true,
message: 'Successfully committed requests to DB',
Expand Down
16 changes: 13 additions & 3 deletions src/router/bundle/bundleGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ export default class BundleGenerator {
};
}

static generateTransactionBundle(baseUrl: string, bundleEntryResponses: BatchReadWriteResponse[]) {
static generateGenericBundle(baseUrl: string, bundleEntryResponses: BatchReadWriteResponse[], bundleType: string) {
const id = uuidv4();
const response = {
resourceType: 'Bundle',
id,
type: 'transaction-response',
type: bundleType,
link: [
{
relation: 'self',
Expand All @@ -103,7 +103,9 @@ export default class BundleGenerator {
const entries: any = [];
bundleEntryResponses.forEach((bundleEntryResponse) => {
let status = '200 OK';
if (bundleEntryResponse.operation === 'create') {
if (bundleEntryResponse.error) {
status = bundleEntryResponse.error;
} else if (bundleEntryResponse.operation === 'create') {
status = '201 Created';
} else if (
['read', 'vread'].includes(bundleEntryResponse.operation) &&
Expand All @@ -129,4 +131,12 @@ export default class BundleGenerator {
response.entry = entries;
return response;
}

static generateTransactionBundle(baseUrl: string, bundleEntryResponses: BatchReadWriteResponse[]) {
return this.generateGenericBundle(baseUrl, bundleEntryResponses, 'transaction-response');
}

static generateBatchBundle(baseUrl: string, bundleEntryResponses: BatchReadWriteResponse[]) {
return this.generateGenericBundle(baseUrl, bundleEntryResponses, 'batch-response');
}
}
137 changes: 129 additions & 8 deletions src/router/bundle/bundleHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ const sampleBundleRequestJSON = {
entry: [],
};

const sampleBatchRequestJSON = {
resourceType: 'Bundle',
type: 'batch',
entry: [],
};

const practitionerDecoded = {
sub: 'fake',
'cognito:groups': ['practitioner'],
Expand Down Expand Up @@ -412,14 +418,6 @@ describe('ERROR Cases: Validation of Bundle request', () => {
// Ensures that for each test, we test the assertions in the catch block
expect.hasAssertions();
});
test('Batch processing', async () => {
const bundleRequestJSON = clone(sampleBundleRequestJSON);
bundleRequestJSON.type = 'batch';

await expect(
bundleHandlerR4.processBatch(bundleRequestJSON, practitionerDecoded, dummyRequestContext, dummyServerUrl),
).rejects.toThrowError(new createError.BadRequest('Currently this server only support transaction Bundles'));
});

test('Bundle V4 JSON format not correct', async () => {
const invalidReadRequest = {
Expand Down Expand Up @@ -660,6 +658,112 @@ describe('SUCCESS Cases: Testing Bundle with CRUD entries', () => {
});
});

describe('SUCCESS Cases: Testing Batch with CRUD entries', () => {
test('empty Batch', async () => {
const bundleRequestJSON = clone(sampleBatchRequestJSON);
bundleRequestJSON.type = 'batch';

await expect(
bundleHandlerR4.processBatch(bundleRequestJSON, practitionerDecoded, dummyRequestContext, dummyServerUrl),
).resolves.toMatchObject({
resourceType: 'Bundle',
id: expect.stringMatching(uuidRegExp),
type: 'batch-response',
link: [
{
relation: 'self',
url: 'https://API_URL.com',
},
],
entry: [],
});
});

test('Handle CRUD requests in a Batch bundle', async () => {
const bundleRequestJSON = clone(sampleBundleRequestJSON);
bundleRequestJSON.entry = bundleRequestJSON.entry.concat(sampleCrudEntries);

const actualResult = await bundleHandlerR4.processBatch(
bundleRequestJSON,
practitionerDecoded,
dummyRequestContext,
dummyServerUrl,
);

const expectedResult = {
resourceType: 'Bundle',
id: expect.stringMatching(uuidRegExp),
type: 'batch-response',
link: [
{
relation: 'self',
url: 'https://API_URL.com',
},
],
entry: [
{
response: {
status: '200 OK',
location: 'Patient/8cafa46d-08b4-4ee4-b51b-803e20ae8126',
etag: '3',
lastModified: '2020-04-23T21:19:35.592Z',
},
},
{
response: {
status: '201 Created',
location: 'Patient/7c7cf4ca-4ba7-4326-b0dd-f3275b735827',
etag: '1',
lastModified: expect.stringMatching(utcTimeRegExp),
},
},
{
resource: {
active: true,
resourceType: 'Patient',
birthDate: '1995-09-24',
meta: {
lastUpdated: expect.stringMatching(utcTimeRegExp),
versionId: '1',
},
managingOrganization: {
reference: 'Organization/2.16.840.1.113883.19.5',
display: 'Good Health Clinic',
},
text: {
div: '<div xmlns="http://www.w3.org/1999/xhtml"><p></p></div>',
status: 'generated',
},
id: '47135b80-b721-430b-9d4b-1557edc64947',
name: [
{
family: 'Langard',
given: ['Abby'],
},
],
gender: 'female',
},
response: {
status: '200 OK',
location: 'Patient/47135b80-b721-430b-9d4b-1557edc64947',
etag: '1',
lastModified: expect.stringMatching(utcTimeRegExp),
},
},
{
response: {
status: '200 OK',
location: 'Patient/bce8411e-c15e-448c-95dd-69155a837405',
etag: '1',
lastModified: expect.stringMatching(utcTimeRegExp),
},
},
],
};
expect(actualResult).toMatchObject(expectedResult);
});
});

describe('ERROR Cases: Bundle not authorized', () => {
test('An entry in Bundle request is not authorized', async () => {
const authZ: Authorization = {
Expand Down Expand Up @@ -709,6 +813,14 @@ describe('ERROR Cases: Bundle not authorized', () => {
dummyServerUrl,
),
).rejects.toThrowError(new UnauthorizedError('An entry within the Bundle is not authorized'));
await expect(
bundleHandlerWithStubbedAuthZ.processBatch(
bundleRequestJSON,
practitionerDecoded,
dummyRequestContext,
dummyServerUrl,
),
).rejects.toThrowError(new UnauthorizedError('An entry within the Bundle is not authorized'));
});

test('After filtering Bundle, read request is not Authorized', async () => {
Expand Down Expand Up @@ -803,6 +915,15 @@ describe('ERROR Cases: Bundle not authorized', () => {
dummyServerUrl,
),
).resolves.toMatchObject(expectedResult);
expectedResult.type = 'batch-response';
await expect(
bundleHandlerWithStubbedAuthZ.processBatch(
bundleRequestJSON,
practitionerDecoded,
dummyRequestContext,
dummyServerUrl,
),
).resolves.toMatchObject(expectedResult);
});
});
describe('SERVER-CAPABILITIES Cases: Validating Bundle request is allowed given server capabilities', () => {
Expand Down
Loading

0 comments on commit 837aff6

Please sign in to comment.