diff --git a/src/api/route-services/__mocks__/experiment.js b/src/api/route-services/__mocks__/experiment.js index f2b2aaf92..c6922cb6f 100644 --- a/src/api/route-services/__mocks__/experiment.js +++ b/src/api/route-services/__mocks__/experiment.js @@ -110,6 +110,8 @@ const mockUpdateProcessingConfig = jest.fn( }), ); +const mockSaveGem2sHandle = jest.fn(() => {}); + const mock = jest.fn().mockImplementation(() => ({ getExperimentData: mockExperimentData, deleteExperiment: mockDeleteExperiment, @@ -118,6 +120,7 @@ const mock = jest.fn().mockImplementation(() => ({ updateCellSets: mockUpdateCellSets, getProcessingConfig: mockGetProcessingConfig, updateProcessingConfig: mockUpdateProcessingConfig, + saveGem2sHandle: mockSaveGem2sHandle, experimentsTableName: 'experiments-test', })); diff --git a/src/specs/api.yaml b/src/specs/api.yaml index 244af64cd..2c42abbb3 100644 --- a/src/specs/api.yaml +++ b/src/specs/api.yaml @@ -557,7 +557,7 @@ paths: '200': description: OK description: Deletes a plot and table for a given experiment with the data specified. - "/experiments/{experimentId}/gem2s": + '/experiments/{experimentId}/gem2s': parameters: - schema: type: string @@ -570,7 +570,7 @@ paths: x-eov-operation-id: gem2s#create x-eov-operation-handler: routes/gem2s responses: - "200": + '200': description: OK content: application/json: @@ -578,7 +578,7 @@ paths: type: object properties: {} description: This path will create a new pipeline that can run a state machine with gem2s tasks. - "/experiments/{experimentId}/pipelines": # this should be changed to /qc at some point + '/experiments/{experimentId}/pipelines': # this should be changed to /qc at some point parameters: - schema: type: string @@ -940,4 +940,4 @@ paths: content: application/json: schema: - $ref: ./models/HTTPError.v1.yaml \ No newline at end of file + $ref: ./models/HTTPError.v1.yaml diff --git a/tests/api/routes/gem2s.test.js b/tests/api/routes/gem2s.test.js new file mode 100644 index 000000000..e3d79b44a --- /dev/null +++ b/tests/api/routes/gem2s.test.js @@ -0,0 +1,179 @@ +const express = require('express'); +const request = require('supertest'); +const https = require('https'); +const _ = require('lodash'); + +const logger = require('../../../src/utils/logging'); +const expressLoader = require('../../../src/loaders/express'); +const CacheSingleton = require('../../../src/cache'); +const gem2sResponse = require('../../../src/api/route-services/gem2s-response'); +const { createGem2SPipeline } = require('../../../src/api/general-services/pipeline-manage'); + +jest.mock('sns-validator'); +jest.mock('aws-xray-sdk'); +jest.mock('../../../src/utils/authMiddlewares'); +jest.mock('../../../src/utils/logging'); +jest.mock('../../../src/cache'); +jest.mock('../../../src/api/route-services/gem2s-response'); +jest.mock('../../../src/api/general-services/pipeline-manage'); +jest.mock('../../../src/api/route-services/experiment'); + +const basicMsg = { + MessageId: 'da8827d4-ffc2-5efb-82c1-70f929b2081d', + ResponseMetadata: { + RequestId: '826314a1-e99f-5fe7-b845-438c3fef9901', + HTTPStatusCode: 200, + HTTPHeaders: { + 'x-amzn-requestid': '826314a1-e99f-5fe7-b845-438c3fef9901', + 'content-type': 'text/xml', + 'content-length': '294', + date: 'Thu, 07 May 2020 09:26:08 GMT', + }, + RetryAttempts: 0, + }, +}; + + +describe('tests for gem2s route', () => { + let app = null; + + beforeEach(async () => { + CacheSingleton.createMock({}); + + const mockApp = await expressLoader(express()); + app = mockApp.app; + }); + + afterEach(() => { + logger.log.mockClear(); + logger.error.mockClear(); + jest.clearAllMocks(); + }); + + it('Can handle valid notifications', async () => { + let validMsg = _.cloneDeep(basicMsg); + validMsg.Type = 'Notification'; + validMsg = JSON.stringify(validMsg); + + gem2sResponse.mockImplementation(() => { }); + + await request(app) + .post('/v1/gem2sResults') + .send(validMsg) + .set('Content-type', 'text/plain') + .expect(200) + .expect('ok'); + + expect(logger.error).toHaveBeenCalledTimes(0); + expect(gem2sResponse).toHaveBeenCalledTimes(1); + }); + + it('Returns nok for invalid notifications', async () => { + let validMsg = _.cloneDeep(basicMsg); + validMsg.Type = 'Notification'; + validMsg = JSON.stringify(validMsg); + + gem2sResponse.mockImplementation(() => { throw new Error(); }); + + await request(app) + .post('/v1/gem2sResults') + .send(validMsg) + .set('Content-type', 'text/plain') + .expect(200) + .expect('nok'); + + expect(logger.error).toHaveBeenCalled(); + expect(gem2sResponse).toHaveBeenCalledTimes(1); + }); + + it('Validating the response throws an error', async () => { + const invalidMsg = JSON.stringify(basicMsg); + https.get = jest.fn(); + + await request(app) + .post('/v1/gem2sResults') + .send(invalidMsg) + .set('Content-type', 'text/plain') + .expect(200) + .expect('nok'); + + expect(logger.error).toHaveBeenCalled(); + expect(https.get).toHaveBeenCalledTimes(0); + }); + + it('Can handle message subscription', async () => { + let validMsg = _.cloneDeep(basicMsg); + validMsg.Type = 'SubscriptionConfirmation'; + validMsg = JSON.stringify(validMsg); + + https.get = jest.fn(); + + await request(app) + .post('/v1/gem2sResults') + .send(validMsg) + .set('Content-type', 'text/plain') + .expect(200); + + expect(logger.error).toHaveBeenCalledTimes(0); + expect(https.get).toHaveBeenCalledTimes(1); + }); + + it('Can handle message unsubscription', async () => { + let validMsg = _.cloneDeep(basicMsg); + validMsg.Type = 'UnsubscribeConfirmation'; + validMsg = JSON.stringify(validMsg); + + https.get = jest.fn(); + + await request(app) + .post('/v1/gem2sResults') + .send(validMsg) + .set('Content-type', 'text/plain') + .expect(200); + + expect(logger.error).toHaveBeenCalledTimes(0); + expect(https.get).toHaveBeenCalledTimes(1); + }); + + it('Returns an error for malformed work', async () => { + const brokenMsg = ''; + + await request(app) + .post('/v1/gem2sResults') + .send(brokenMsg) + .set('Content-type', 'text/plain') + .expect(200) + .expect('nok'); + }); + + it('Returns an error when message in sns is malformed', async () => { + let validMsg = _.cloneDeep(basicMsg); + validMsg.Type = 'NotificationMalformed'; + validMsg = JSON.stringify(validMsg); + + gem2sResponse.mockImplementation(() => { }); + + await request(app) + .post('/v1/gem2sResults') + .send(validMsg) + .set('Content-type', 'text/plain') + .expect(200) + .expect('nok'); + + expect(logger.error).toHaveBeenCalled(); + }); + + it('Creates a new pipeline for gem2s execution', async (done) => { + createGem2SPipeline.mockReturnValue({}); + + request(app) + .post('/v1/experiments/someId/gem2s') + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + return done(); + }); + }); +}); diff --git a/tests/api/routes/pipelines.test.js b/tests/api/routes/pipelines.test.js index 962ec2b3d..ae26dde76 100644 --- a/tests/api/routes/pipelines.test.js +++ b/tests/api/routes/pipelines.test.js @@ -1,4 +1,3 @@ - const express = require('express'); const request = require('supertest'); const https = require('https'); @@ -12,7 +11,7 @@ jest.mock('aws-xray-sdk'); jest.mock('../../../src/utils/logging'); jest.mock('../../../src/cache'); -const basicMsg = JSON.stringify({ +const basicMsg = { MessageId: 'da8827d4-ffc2-5efb-82c1-70f929b2081d', ResponseMetadata: { RequestId: '826314a1-e99f-5fe7-b845-438c3fef9901', @@ -25,7 +24,7 @@ const basicMsg = JSON.stringify({ }, RetryAttempts: 0, }, -}); +}; describe('PipelineResults route', () => { @@ -44,7 +43,7 @@ describe('PipelineResults route', () => { }); it('Can handle notifications', async () => { - let validMsg = _.cloneDeep(JSON.parse(basicMsg)); + let validMsg = _.cloneDeep(basicMsg); validMsg.Type = 'Notification'; validMsg = JSON.stringify(validMsg); @@ -62,7 +61,7 @@ describe('PipelineResults route', () => { }); it('Validating the response throws an error', async () => { - const invalidMsg = _.cloneDeep(basicMsg); + const invalidMsg = JSON.stringify(basicMsg); https.get = jest.fn(); await request(app) @@ -76,8 +75,8 @@ describe('PipelineResults route', () => { expect(https.get).toHaveBeenCalledTimes(0); }); - it('Can handle message subscribtion', async () => { - let validMsg = _.cloneDeep(JSON.parse(basicMsg)); + it('Can handle message subscription', async () => { + let validMsg = _.cloneDeep(basicMsg); validMsg.Type = 'SubscriptionConfirmation'; validMsg = JSON.stringify(validMsg); @@ -93,8 +92,8 @@ describe('PipelineResults route', () => { expect(https.get).toHaveBeenCalledTimes(1); }); - it('Can handle message unsubscribtion', async () => { - let validMsg = _.cloneDeep(JSON.parse(basicMsg)); + it('Can handle message unsubscription', async () => { + let validMsg = _.cloneDeep(basicMsg); validMsg.Type = 'UnsubscribeConfirmation'; validMsg = JSON.stringify(validMsg); @@ -110,8 +109,8 @@ describe('PipelineResults route', () => { expect(https.get).toHaveBeenCalledTimes(1); }); - it('Get malformatted work results returns an error', async () => { - const brokenMsg = JSON.stringify(); + it('Returns an error for malformed work', async () => { + const brokenMsg = ''; await request(app) .post('/v1/pipelineResults') @@ -122,7 +121,7 @@ describe('PipelineResults route', () => { }); it('Returns an error when message in sns is malformed', async () => { - let validMsg = _.cloneDeep(JSON.parse(basicMsg)); + let validMsg = _.cloneDeep(basicMsg); validMsg.Type = 'NotificationMalformed'; validMsg = JSON.stringify(validMsg);