diff --git a/.vscode/settings.json b/.vscode/settings.json index 5fed8149c..7afb7dc9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "[javascript]": { "editor.tabSize": 2, diff --git a/src/api.v2/controllers/cellLevelMetaController.js b/src/api.v2/controllers/cellLevelMetaController.js index 2bd8cf734..e0c210534 100644 --- a/src/api.v2/controllers/cellLevelMetaController.js +++ b/src/api.v2/controllers/cellLevelMetaController.js @@ -4,7 +4,7 @@ const bucketNames = require('../../config/bucketNames'); const sqlClient = require('../../sql/sqlClient'); const CellLevelMeta = require('../model/CellLevelMeta'); const CellLevelMetaToExperiment = require('../model/CellLevelMetaToExperiment'); -const { getFileUploadUrls } = require('../helpers/s3/signedUrl'); +const { createMultipartUpload } = require('../helpers/s3/signedUrl'); const getLogger = require('../../utils/getLogger'); const OK = require('../../utils/responses/OK'); @@ -28,7 +28,7 @@ const upload = async (req, res) => { await new CellLevelMeta(trx).create(newCellLevelMetaFile); await new CellLevelMetaToExperiment(trx).setNewFile(experimentId, cellLevelMetaKey); - uploadUrlParams = await getFileUploadUrls(cellLevelMetaKey, {}, size, bucketName); + uploadUrlParams = await createMultipartUpload(cellLevelMetaKey, {}, bucketName); uploadUrlParams = { ...uploadUrlParams, fileId: cellLevelMetaKey }; }); diff --git a/src/api.v2/controllers/sampleFileController.js b/src/api.v2/controllers/sampleFileController.js index bfea1b9dd..6fae75064 100644 --- a/src/api.v2/controllers/sampleFileController.js +++ b/src/api.v2/controllers/sampleFileController.js @@ -4,7 +4,7 @@ const Sample = require('../model/Sample'); const SampleFile = require('../model/SampleFile'); const bucketNames = require('../../config/bucketNames'); -const { getFileUploadUrls, getSampleFileDownloadUrl } = require('../helpers/s3/signedUrl'); +const { getSampleFileDownloadUrl, createMultipartUpload } = require('../helpers/s3/signedUrl'); const { OK, MethodNotAllowedError } = require('../../utils/responses'); const getLogger = require('../../utils/getLogger'); @@ -25,8 +25,6 @@ const createFile = async (req, res) => { upload_status: 'uploading', }; - - await sqlClient.get().transaction(async (trx) => { await new SampleFile(trx).create(newSampleFile); await new Sample(trx).setNewFile(sampleId, sampleFileId, sampleFileType); @@ -41,7 +39,7 @@ const createFile = async (req, res) => { const beginUpload = async (req, res) => { const { params: { experimentId, sampleFileId }, - body: { metadata, size }, + body: { metadata }, } = req; const { uploadStatus } = await new SampleFile().findById(sampleFileId).first(); @@ -55,9 +53,9 @@ const beginUpload = async (req, res) => { ); } - logger.log(`Generating multipart upload urls for ${experimentId}, sample file ${sampleFileId}`); - const uploadParams = await getFileUploadUrls( - sampleFileId, metadata, size, bucketNames.SAMPLE_FILES, + logger.log(`Creating multipart upload for ${experimentId}, sample file ${sampleFileId}`); + const uploadParams = await createMultipartUpload( + sampleFileId, metadata, bucketNames.SAMPLE_FILES, ); res.json(uploadParams); diff --git a/src/api.v2/controllers/uploadController.js b/src/api.v2/controllers/uploadController.js new file mode 100644 index 000000000..16b611df6 --- /dev/null +++ b/src/api.v2/controllers/uploadController.js @@ -0,0 +1,20 @@ +const { getPartUploadSignedUrl } = require('../helpers/s3/signedUrl'); + +const getLogger = require('../../utils/getLogger'); + +const logger = getLogger('[uploadController] - '); + +const getUploadPartSignedUrl = async (req, res) => { + const { experimentId, uploadId, partNumber } = req.params; + const { bucket, key } = req.query; + + logger.log(`Experiment: ${experimentId}, getting part ${partNumber} for upload: ${uploadId}`); + + const signedUrl = await getPartUploadSignedUrl(key, bucket, uploadId, partNumber); + + logger.log(`Finished getting part ${partNumber} for experiment ${experimentId}, upload: ${uploadId}`); + + res.json(signedUrl); +}; + +module.exports = { getUploadPartSignedUrl }; diff --git a/src/api.v2/helpers/s3/signedUrl.js b/src/api.v2/helpers/s3/signedUrl.js index 1bb0dee2c..300729658 100644 --- a/src/api.v2/helpers/s3/signedUrl.js +++ b/src/api.v2/helpers/s3/signedUrl.js @@ -22,11 +22,9 @@ const getSignedUrl = async (operation, params) => { return s3.getSignedUrlPromise(operation, params); }; -const FILE_CHUNK_SIZE = 10000000; - -const createMultipartUpload = async (params, size) => { - if (!params.Bucket) throw new Error('Bucket is required'); - if (!params.Key) throw new Error('Key is required'); +const createMultipartUpload = async (key, metadata, bucket) => { + if (!key) throw new Error('key is required'); + if (!bucket) throw new Error('bucket is required'); const S3Config = { apiVersion: '2006-03-01', @@ -34,35 +32,51 @@ const createMultipartUpload = async (params, size) => { region: config.awsRegion, }; - const s3 = new AWS.S3(S3Config); - - const { UploadId } = await s3.createMultipartUpload(params).promise(); - - const baseParams = { - ...params, - UploadId, + const params = { + Bucket: bucket, + Key: key, + // 1 hour timeout of upload link + Expires: 3600, }; - const promises = []; - const parts = Math.ceil(size / FILE_CHUNK_SIZE); - - for (let i = 0; i < parts; i += 1) { - promises.push( - s3.getSignedUrlPromise('uploadPart', { - ...baseParams, - PartNumber: i + 1, - }), - ); + if (metadata.cellrangerVersion) { + params.Metadata = { + cellranger_version: metadata.cellrangerVersion, + }; } - const signedUrls = await Promise.all(promises); + const s3 = new AWS.S3(S3Config); + + // @ts-ignore + const { UploadId } = await s3.createMultipartUpload(params).promise(); return { - signedUrls, uploadId: UploadId, + bucket, + key, }; }; +const getPartUploadSignedUrl = async (key, bucketName, uploadId, partNumber) => { + const S3Config = { + apiVersion: '2006-03-01', + signatureVersion: 'v4', + region: config.awsRegion, + }; + + const s3 = new AWS.S3(S3Config); + + const params = { + Key: key, + Bucket: bucketName, + // 1 hour timeout of upload link + Expires: 3600, + UploadId: uploadId, + PartNumber: partNumber, + }; + + return await s3.getSignedUrlPromise('uploadPart', params); +}; const completeMultipartUpload = async (key, parts, uploadId, bucketName) => { const params = { @@ -82,23 +96,6 @@ const completeMultipartUpload = async (key, parts, uploadId, bucketName) => { await s3.completeMultipartUpload(params).promise(); }; -const getFileUploadUrls = async (key, metadata, size, bucketName) => { - const params = { - Bucket: bucketName, - Key: key, - // 1 hour timeout of upload link - Expires: 3600, - }; - - if (metadata.cellrangerVersion) { - params.Metadata = { - cellranger_version: metadata.cellrangerVersion, - }; - } - - return await createMultipartUpload(params, size); -}; - const fileNameToReturn = { matrix10x: 'matrix.mtx.gz', barcodes10x: 'barcodes.tsv.gz', @@ -132,9 +129,9 @@ const getSampleFileDownloadUrl = async (experimentId, sampleId, fileType) => { }; module.exports = { - getFileUploadUrls, getSampleFileDownloadUrl, getSignedUrl, createMultipartUpload, completeMultipartUpload, + getPartUploadSignedUrl, }; diff --git a/src/api.v2/routes/upload.js b/src/api.v2/routes/upload.js new file mode 100644 index 000000000..4b75ec088 --- /dev/null +++ b/src/api.v2/routes/upload.js @@ -0,0 +1,9 @@ +const { getUploadPartSignedUrl } = require('../controllers/uploadController'); +const { expressAuthorizationMiddleware } = require('../middlewares/authMiddlewares'); + +module.exports = { + 'upload#getUploadPartSignedUrl': [ + expressAuthorizationMiddleware, + (req, res, next) => getUploadPartSignedUrl(req, res).catch(next), + ], +}; diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index 85dcabc52..19f5e8757 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -1114,8 +1114,8 @@ paths: properties: name: type: string - size: - type: integer + required: + - name responses: "200": description: Success @@ -1636,6 +1636,70 @@ paths: schema: $ref: "#/components/schemas/HTTPError" + "/experiments/{experimentId}/upload/{uploadId}/part/{partNumber}/signedUrl": + get: + summary: Gets signed url to upload a part + description: Gets the upload url for uploading partNumber of an ongoing multipart upload + operationId: getUploadPartSignedUrl + x-eov-operation-id: upload#getUploadPartSignedUrl + x-eov-operation-handler: routes/upload + parameters: + - name: experimentId + required: true + in: path + description: ID of the experiment. + schema: + type: string + - name: uploadId + in: path + description: UploadId used to identify s3 multipart uploads. + required: true + schema: + type: string + - name: partNumber + in: path + description: PartNumber used to identify which part of the s3 multipart upload this is. + required: true + schema: + type: integer + - name: bucket + description: Bucket receiving the upload + schema: + type: string + in: query + required: true + - name: key + description: Key of the file being uploaded in the bucket + schema: + type: string + in: query + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + type: string + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/HTTPError" + "401": + description: The request lacks authentication credentials. + content: + application/json: + schema: + $ref: "#/components/schemas/HTTPError" + "403": + description: Forbidden request for this user. + content: + application/json: + schema: + $ref: "#/components/schemas/HTTPError" + "/experiments/{experimentId}/sampleFiles/{sampleFileId}/beginUpload": post: summary: Begins the multipart upload of a sample file diff --git a/src/specs/models/samples-bodies/BeginSampleFileUpload.v2.yaml b/src/specs/models/samples-bodies/BeginSampleFileUpload.v2.yaml index 427212573..3442cc307 100644 --- a/src/specs/models/samples-bodies/BeginSampleFileUpload.v2.yaml +++ b/src/specs/models/samples-bodies/BeginSampleFileUpload.v2.yaml @@ -2,8 +2,6 @@ title: Begin sample file multipart upload description: 'Data to begin an s3 multipart upload' type: object properties: - size: - type: number metadata: description: Metadata to append to the s3 object on upload type: object @@ -15,7 +13,6 @@ properties: - pattern: v3 required: - - size - metadata additionalProperties: false \ No newline at end of file diff --git a/tests/api.v2/controllers/cellLevelMetaController.test.js b/tests/api.v2/controllers/cellLevelMetaController.test.js index b408ee52f..ebb9bcf65 100644 --- a/tests/api.v2/controllers/cellLevelMetaController.test.js +++ b/tests/api.v2/controllers/cellLevelMetaController.test.js @@ -2,8 +2,9 @@ const cellLevelMetaController = require('../../../src/api.v2/controllers/cellLevelMetaController'); const CellLevelMeta = require('../../../src/api.v2/model/CellLevelMeta'); const CellLevelMetaToExperiment = require('../../../src/api.v2/model/CellLevelMetaToExperiment'); -const { getFileUploadUrls } = require('../../../src/api.v2/helpers/s3/signedUrl'); +const { createMultipartUpload } = require('../../../src/api.v2/helpers/s3/signedUrl'); const { OK } = require('../../../src/utils/responses'); +const bucketNames = require('../../../src/config/bucketNames'); const { mockSqlClient, mockTrx } = require('../mocks/getMockSqlClient')(); jest.mock('../../../src/api.v2/model/CellLevelMeta'); @@ -33,13 +34,13 @@ describe('CellLevelMetaController', () => { const mockUploadUrlParams = { url: 'signedUrl', fileId: 'fileId' }; - getFileUploadUrls.mockResolvedValue(mockUploadUrlParams); + createMultipartUpload.mockResolvedValue(mockUploadUrlParams); await cellLevelMetaController.upload(mockReq, mockRes); expect(CellLevelMeta).toHaveBeenCalledWith(mockTrx); expect(CellLevelMetaToExperiment).toHaveBeenCalledWith(mockTrx); - expect(getFileUploadUrls).toHaveBeenCalledWith( - expect.any(String), {}, mockReq.body.size, expect.any(String), + expect(createMultipartUpload).toHaveBeenCalledWith( + expect.any(String), {}, bucketNames.CELL_LEVEL_META, ); expect(mockRes.json).toHaveBeenCalledWith( diff --git a/tests/api.v2/controllers/sampleFileController.test.js b/tests/api.v2/controllers/sampleFileController.test.js index c31771491..c6c0140c8 100644 --- a/tests/api.v2/controllers/sampleFileController.test.js +++ b/tests/api.v2/controllers/sampleFileController.test.js @@ -25,12 +25,12 @@ const mockRes = { }; describe('sampleFileController', () => { - const mockUploadParams = { signedUrls: ['signedUrl1', 'signedUrl2'], fileId: 'mockfileId' }; + const mockUploadParams = { fileId: 'mockfileId', bucket: 'mockBucket', key: 'mockKey' }; beforeEach(async () => { jest.clearAllMocks(); - signedUrl.getFileUploadUrls.mockReturnValue(Promise.resolve(mockUploadParams)); + signedUrl.createMultipartUpload.mockReturnValueOnce(Promise.resolve(mockUploadParams)); }); it('createFile works correctly', async () => { @@ -104,10 +104,6 @@ describe('sampleFileController', () => { await sampleFileController.beginUpload(mockReq, mockRes); - expect(signedUrl.getFileUploadUrls).toHaveBeenCalledWith( - sampleFileId, metadata, size, bucketNames.SAMPLE_FILES, - ); - expect(mockRes.json).toHaveBeenCalledWith(mockUploadParams); expect(sampleFileInstance.findById.mock.calls).toMatchSnapshot(); @@ -128,10 +124,6 @@ describe('sampleFileController', () => { await sampleFileController.beginUpload(mockReq, mockRes); - expect(signedUrl.getFileUploadUrls).toHaveBeenCalledWith( - sampleFileId, metadata, size, bucketNames.SAMPLE_FILES, - ); - expect(mockRes.json).toHaveBeenCalledWith(mockUploadParams); expect(sampleFileInstance.findById.mock.calls).toMatchSnapshot(); @@ -157,7 +149,6 @@ describe('sampleFileController', () => { ), ); - expect(signedUrl.getFileUploadUrls).not.toHaveBeenCalled(); expect(mockRes.json).not.toHaveBeenCalled(); }); diff --git a/tests/api.v2/controllers/uploadController.test.js b/tests/api.v2/controllers/uploadController.test.js new file mode 100644 index 000000000..908984906 --- /dev/null +++ b/tests/api.v2/controllers/uploadController.test.js @@ -0,0 +1,40 @@ +// @ts-nocheck +const { getUploadPartSignedUrl } = require('../../../src/api.v2/controllers/uploadController'); +const signedUrl = require('../../../src/api.v2/helpers/s3/signedUrl'); + +jest.mock('../../../src/api.v2/helpers/s3/signedUrl'); + +const mockRes = { + json: jest.fn(), +}; + +describe('sampleFileController', () => { + const mockSignedUrl = 'mockSignedUrl'; + + const mockExperimentId = 'mockExperimentId'; + const uploadId = 'mockUploadId'; + const partNumber = 'mockPartNumber1'; + const bucket = 'mockBucket'; + const key = 'mockKey'; + + beforeEach(async () => { + jest.clearAllMocks(); + + signedUrl.getPartUploadSignedUrl.mockReturnValueOnce(Promise.resolve(mockSignedUrl)); + }); + + it('getPartUploadSignedUrl works correctly', async () => { + const mockReq = { + params: { mockExperimentId, uploadId, partNumber }, + query: { bucket, key }, + }; + + await getUploadPartSignedUrl(mockReq, mockRes); + + expect(signedUrl.getPartUploadSignedUrl).toHaveBeenCalledWith( + key, bucket, uploadId, partNumber, + ); + + expect(mockRes.json).toHaveBeenCalledWith(mockSignedUrl); + }); +}); diff --git a/tests/api.v2/helpers/s3/__snapshots__/signedUrl.test.js.snap b/tests/api.v2/helpers/s3/__snapshots__/signedUrl.test.js.snap index 38032893d..f9ad13170 100644 --- a/tests/api.v2/helpers/s3/__snapshots__/signedUrl.test.js.snap +++ b/tests/api.v2/helpers/s3/__snapshots__/signedUrl.test.js.snap @@ -35,165 +35,6 @@ exports[`completeMultipartUpload works correctly 1`] = ` } `; -exports[`getFileUploadUrls works correctly with metadata cellrangerVersion 1`] = ` -[MockFunction] { - "calls": Array [ - Array [ - Object { - "Bucket": "biomage-originals-test-000000000000", - "Expires": 3600, - "Key": "mockSampleFileId", - }, - ], - Array [ - Object { - "Bucket": "biomage-originals-test-000000000000", - "Expires": 3600, - "Key": "mockSampleFileId", - "Metadata": Object { - "cellranger_version": "v2", - }, - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": Object { - "promise": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": Object { - "UploadId": "uploadId", - }, - }, - ], - }, - }, - }, - Object { - "type": "return", - "value": Object { - "promise": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": Object { - "UploadId": "uploadId", - }, - }, - ], - }, - }, - }, - ], -} -`; - -exports[`getFileUploadUrls works correctly with metadata cellrangerVersion 2`] = ` -[MockFunction] { - "calls": Array [ - Array [ - "uploadPart", - Object { - "Bucket": "biomage-originals-test-000000000000", - "Expires": 3600, - "Key": "mockSampleFileId", - "PartNumber": 1, - "UploadId": "uploadId", - }, - ], - Array [ - "uploadPart", - Object { - "Bucket": "biomage-originals-test-000000000000", - "Expires": 3600, - "Key": "mockSampleFileId", - "Metadata": Object { - "cellranger_version": "v2", - }, - "PartNumber": 1, - "UploadId": "uploadId", - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": "signedUrl", - }, - Object { - "type": "return", - "value": "signedUrl", - }, - ], -} -`; - -exports[`getFileUploadUrls works correctly without metadata 1`] = ` -[MockFunction] { - "calls": Array [ - Array [ - Object { - "Bucket": "biomage-originals-test-000000000000", - "Expires": 3600, - "Key": "mockSampleFileId", - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": Object { - "promise": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": Object { - "UploadId": "uploadId", - }, - }, - ], - }, - }, - }, - ], -} -`; - -exports[`getFileUploadUrls works correctly without metadata 2`] = ` -[MockFunction] { - "calls": Array [ - Array [ - "uploadPart", - Object { - "Bucket": "biomage-originals-test-000000000000", - "Expires": 3600, - "Key": "mockSampleFileId", - "PartNumber": 1, - "UploadId": "uploadId", - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": "signedUrl", - }, - ], -} -`; - exports[`getSampleFileDownloadUrl works correctly 1`] = ` [MockFunction] { "calls": Array [ diff --git a/tests/api.v2/helpers/s3/signedUrl.test.js b/tests/api.v2/helpers/s3/signedUrl.test.js index aef38f59c..d627310da 100644 --- a/tests/api.v2/helpers/s3/signedUrl.test.js +++ b/tests/api.v2/helpers/s3/signedUrl.test.js @@ -1,13 +1,12 @@ // @ts-nocheck const SampleFile = require('../../../../src/api.v2/model/SampleFile'); const signedUrl = require('../../../../src/api.v2/helpers/s3/signedUrl'); -const bucketNames = require('../../../../src/config/bucketNames'); const AWS = require('../../../../src/utils/requireAWS'); const { NotFoundError } = require('../../../../src/utils/responses'); const { - getSignedUrl, getSampleFileDownloadUrl, getFileUploadUrls, completeMultipartUpload, + getSignedUrl, getSampleFileDownloadUrl, completeMultipartUpload, } = signedUrl; const sampleFileInstance = new SampleFile(); @@ -68,42 +67,6 @@ describe('getSignedUrl', () => { }); }); -describe('getFileUploadUrls', () => { - const mockSampleFileId = 'mockSampleFileId'; - - const signedUrlResponse = { signedUrls: ['signedUrl'], uploadId: 'uploadId' }; - - const createMultipartUploadSpy = jest.fn(); - const getSignedUrlPromiseSpy = jest.fn(); - - beforeEach(() => { - createMultipartUploadSpy.mockReturnValue({ promise: jest.fn().mockReturnValue({ UploadId: 'uploadId' }) }); - getSignedUrlPromiseSpy.mockReturnValue('signedUrl'); - - AWS.S3.mockReset(); - AWS.S3.mockImplementation(() => ({ - createMultipartUpload: createMultipartUploadSpy, - getSignedUrlPromise: getSignedUrlPromiseSpy, - })); - }); - - it('works correctly without metadata', async () => { - const response = await getFileUploadUrls(mockSampleFileId, {}, 1, bucketNames.SAMPLE_FILES); - - expect(response).toEqual(signedUrlResponse); - expect(createMultipartUploadSpy).toMatchSnapshot(); - expect(getSignedUrlPromiseSpy).toMatchSnapshot(); - }); - - it('works correctly with metadata cellrangerVersion', async () => { - const response = await getFileUploadUrls(mockSampleFileId, { cellrangerVersion: 'v2' }, 1, bucketNames.SAMPLE_FILES); - - expect(response).toEqual(signedUrlResponse); - expect(createMultipartUploadSpy).toMatchSnapshot(); - expect(getSignedUrlPromiseSpy).toMatchSnapshot(); - }); -}); - describe('completeMultipartUpload', () => { const mockSampleFileId = 'mockSampleFileId'; const mockParts = []; @@ -128,6 +91,30 @@ describe('completeMultipartUpload', () => { }); }); +describe('getPartUploadSignedUrl', () => { + const signedUrlPromiseSpy = jest.fn(); + + beforeEach(() => { + AWS.S3.mockReset(); + AWS.S3.mockImplementation(() => ({ + getSignedUrlPromise: signedUrlPromiseSpy, + })); + }); + + it('works correctly', async () => { + const mockSignedUrl = 'mockSignedUrl'; + signedUrlPromiseSpy.mockResolvedValueOnce(mockSignedUrl); + + const key = 'mockKey'; + const bucketName = 'mockBucket'; + const uploadId = 'mockUploadId'; + const partNumber = 'mockPartNumber'; + + const response = await signedUrl.getPartUploadSignedUrl(key, bucketName, uploadId, partNumber); + + expect(response).toEqual(mockSignedUrl); + }); +}); describe('getSampleFileDownloadUrl', () => { const experimentId = 'mockExperimentId'; diff --git a/tests/api.v2/routes/cellLevelMeta.test.js b/tests/api.v2/routes/cellLevelMeta.test.js index 5a8c761c2..81accd59f 100644 --- a/tests/api.v2/routes/cellLevelMeta.test.js +++ b/tests/api.v2/routes/cellLevelMeta.test.js @@ -13,7 +13,7 @@ jest.mock('../../../src/api.v2/middlewares/authMiddlewares'); const experimentId = 'experiment-id'; const newCellLevelFileData = { - id: experimentId, + name: 'metaDataName', }; describe('cell level metadata route', () => { @@ -41,6 +41,7 @@ describe('cell level metadata route', () => { return done(); }); }); + it('downloading a new cell level meta file results in a successful response', async (done) => { cellLevelMetaController.download.mockImplementationOnce((req, res) => { res.json(OK()); diff --git a/tests/api.v2/routes/sampleFile.test.js b/tests/api.v2/routes/sampleFile.test.js index 3ae2de73d..c701d3dd6 100644 --- a/tests/api.v2/routes/sampleFile.test.js +++ b/tests/api.v2/routes/sampleFile.test.js @@ -153,10 +153,9 @@ describe('tests for experiment route', () => { const experimentId = 'mockExperimentId'; const sampleFileId = 'mockSampleFileId'; - const size = 10; const metadata = {}; - const body = { metadata, size }; + const body = { metadata }; request(app) .post(`/v2/experiments/${experimentId}/sampleFiles/${sampleFileId}/beginUpload`) diff --git a/tests/api.v2/routes/upload.test.js b/tests/api.v2/routes/upload.test.js new file mode 100644 index 000000000..36b1620ab --- /dev/null +++ b/tests/api.v2/routes/upload.test.js @@ -0,0 +1,62 @@ +// @ts-nocheck + +const express = require('express'); +const request = require('supertest'); +const expressLoader = require('../../../src/loaders/express'); + +const uploadController = require('../../../src/api.v2/controllers/uploadController'); + +jest.mock('../../../src/api.v2/controllers/uploadController', () => ({ + getUploadPartSignedUrl: jest.fn(), +})); + +jest.mock('../../../src/api.v2/middlewares/authMiddlewares'); + +describe('tests for upload routes', () => { + let app; + + beforeEach(async () => { + const mockApp = await expressLoader(express()); + app = mockApp.app; + }); + + it('getUploadPartSignedUrl results in a successful response', async (done) => { + uploadController.getUploadPartSignedUrl.mockImplementationOnce((req, res) => { + res.json('mockSignedUrl'); + return Promise.resolve(); + }); + + const queryParams = new URLSearchParams({ bucket: 'mockBucket', key: 'mockKey' }); + + request(app) + .get(`/v2/experiments/mockExperimentId/upload/mockUploadId/part/1/signedUrl?${queryParams}`) + .expect(JSON.stringify('mockSignedUrl')) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); + + it('getUploadPartSignedUrl fails 400 if query params are missing', async (done) => { + uploadController.getUploadPartSignedUrl.mockImplementationOnce((req, res) => { + res.json('mockSignedUrl'); + return Promise.resolve(); + }); + + request(app) + .get('/v2/experiments/mockExperimentId/upload/mockUploadId/part/1/signedUrl') + .expect(400) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); +});