diff --git a/app/package-lock.json b/app/package-lock.json index c795c835..680d5ab3 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -13,7 +13,6 @@ "@aws-sdk/lib-storage": "^3.388.0", "@aws-sdk/s3-request-presigner": "^3.388.0", "api-problem": "^9.0.1", - "busboy": "^1.6.0", "compression": "^1.7.4", "config": "^3.3.9", "content-disposition": "^0.5.4", @@ -4613,17 +4612,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -12519,14 +12507,6 @@ "readable-stream": "^3.5.0" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -17312,14 +17292,6 @@ "run-applescript": "^5.0.0" } }, - "busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "requires": { - "streamsearch": "^1.1.0" - } - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -23296,11 +23268,6 @@ "readable-stream": "^3.5.0" } }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/app/package.json b/app/package.json index b1e42509..d05f88ea 100644 --- a/app/package.json +++ b/app/package.json @@ -33,7 +33,6 @@ "@aws-sdk/lib-storage": "^3.388.0", "@aws-sdk/s3-request-presigner": "^3.388.0", "api-problem": "^9.0.1", - "busboy": "^1.6.0", "compression": "^1.7.4", "config": "^3.3.9", "content-disposition": "^0.5.4", diff --git a/app/src/components/utils.js b/app/src/components/utils.js index 20f4f89d..49350bce 100644 --- a/app/src/components/utils.js +++ b/app/src/components/utils.js @@ -224,30 +224,6 @@ const utils = { return array.find(obj => (obj.key === key && obj.value === value)); }, - /** - * @deprecated To be removed in v0.6.0 - * @function getPath - * Gets the relative path of `objId` - * @param {string} objId The object id - * @returns {Promise} The path - */ - async getPath(objId) { - let key = utils.delimit(config.get('objectStorage.key')); - - // Function scoped import to avoid circular dependencies - const { objectService } = require('../services'); - - try { - key = (await objectService.getBucketKey(objId)).key; - } catch (err) { - log.verbose(`${err.message}. Using default fallback path instead.`, { - function: 'getPath', objId: objId - }); - } - - return utils.joinPath(key, objId); - }, - /** * @function getS3VersionId * Gets the s3VersionId from database using given internal COMS version id diff --git a/app/src/controllers/object.js b/app/src/controllers/object.js index ccfcfb02..c94b30b5 100644 --- a/app/src/controllers/object.js +++ b/app/src/controllers/object.js @@ -1,5 +1,4 @@ const Problem = require('api-problem'); -const busboy = require('busboy'); const config = require('config'); const cors = require('cors'); const { v4: uuidv4, NIL: SYSTEM_USER } = require('uuid'); @@ -339,159 +338,6 @@ const controller = { } }, - /** - * @function createObjectPost - * @deprecated - * Creates a new object - * @param {object} req Express request object - * @param {object} res Express response object - * @param {function} next The next callback function - * @returns {function} Express middleware function - */ - async createObjectPost(req, res, next) { - let data; - let s3Promise; - - try { - const bb = busboy({ - headers: req.headers, - limits: { - fields: 0, - fileSize: MAXFILEOBJECTLENGTH, - files: 1 - } - }); - const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, SYSTEM_USER)); - - // Preflight CREATE permission check if bucket scoped and OIDC authenticated - const bucketId = req.query.bucketId ? addDashesToUuid(req.query.bucketId) : undefined; - if (bucketId && userId) { - const permission = await bucketPermissionService.searchPermissions({ userId: userId, bucketId: bucketId, permCode: 'CREATE' }); - if (!permission.length) { - return new Problem(403, { detail: 'User lacks permission to complete this action' }).send(res); - } - } - - // Preflight existence check for bucketId - const { key: bucketKey } = await getBucket(bucketId); - - bb.on('file', async (name, stream, info) => { - try { - // Only process multipart file fields named 'file'. - if (name !== 'file') { - req.unpipe(bb); - throw new Problem(400, { detail: 'Malformed body: form-data expects \'file\' field' }); - } - - const handleNewFile = () => { - const objId = uuidv4(); - data = { - id: objId, - bucketId: bucketId, - name: info.filename, - mimeType: info.mimeType, - metadata: getMetadata(req.headers), - tags: { - ...req.query.tagset, - 'coms-id': objId // Enforce `coms-id:` tag - } - }; - - // TODO: Consider adding in PutObject analog for sub 5GB file operations - const contentLength = parseInt(req.get('Content-Length')); - if (contentLength < MAXFILEOBJECTLENGTH) { - s3Promise = storageService.upload({ ...data, stream: stream }); - } else { - req.unpipe(bb); - throw new Problem(413, { detail: 'File exceeds maximum 50GB limit' }); - } - }; - - try { - // Preflight S3 Object check - await storageService.headObject({ - filePath: joinPath(bucketKey, info.filename), - bucketId: bucketId - }); - - // Hard short circuit skip file as the object already exists on bucket - req.unpipe(bb); - throw new Problem(409, { detail: 'Bucket already contains object' }); - } catch (err) { - if (err instanceof Problem) throw err; - - // Object does not exist on bucket - if (err.$metadata?.httpStatusCode !== 404) { - // Skip file in the unlikely event we get an unexpected error from headObject - req.unpipe(bb); - throw new Problem(502, { detail: 'Bucket communication error' }); - } - - handleNewFile(); - } - } catch (e) { - next(errorToProblem(SERVICE, e)); - } - }); - - bb.on('close', async () => { - try { - const s3Response = await s3Promise; - const dbResponse = await utils.trxWrapper(async (trx) => { - // Create Object - const object = await objectService.create({ - ...data, - userId: userId, - path: joinPath(bucketKey, data.name) - }, trx); - - // Create Version - const s3VersionId = s3Response.VersionId ?? null; - const version = await versionService.create({ - ...data, - etag: s3Response.ETag, - s3VersionId: s3VersionId, - isLatest: true - }, userId, trx); - object.versionId = version.id; - - // Create Metadata - if (data.metadata && Object.keys(data.metadata).length) { - await metadataService.associateMetadata(version.id, getKeyValue(data.metadata), userId, trx); - } - - // Create Tags - if (data.tags && Object.keys(data.tags).length) { - await tagService.associateTags(version.id, getKeyValue(data.tags), userId, trx); - } - - return object; - }); - - res.status(200).json({ - ...data, - ...dbResponse, - ...renameObjectProperty(s3Response, 'VersionId', 's3VersionId') - }); - } catch (e) { - next(errorToProblem(SERVICE, e)); - } - }); - - bb.on('error', (e) => { - throw new Error(`Stream Error: ${e}`); // TODO: Revisit on error stack refactor - }); - - req.connection.on('error', (e) => { - throw new Error(`Connection Error: ${e}`); // TODO: Revisit on error stack refactor - }); - - req.pipe(bb); - } catch (e) { - next(errorToProblem(SERVICE, e)); - } - }, - /** * @function deleteMetadata * Creates a new version of the object via copy with the given metadata removed @@ -1168,156 +1014,6 @@ const controller = { } catch (e) { next(errorToProblem(SERVICE, e)); } - }, - - /** - * @function updateObjectPost - * @deprecated - * Creates an updated version of the object via streaming - * @param {object} req Express request object - * @param {object} res Express response object - * @param {function} next The next callback function - * @returns {function} Express middleware function - */ - async updateObjectPost(req, res, next) { - let data; - let s3Promise; - - try { - const bb = busboy({ - headers: req.headers, - limits: { - fields: 0, - fileSize: MAXFILEOBJECTLENGTH, - files: 1 - } - }); - const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, SYSTEM_USER)); - - // Preflight existence check for bucketId - const bucketId = req.currentObject?.bucketId; - const { key: bucketKey } = await getBucket(bucketId); - - bb.on('file', async (name, stream, info) => { - try { - // Only process multipart file fields named 'file'. - if (name !== 'file') { - req.unpipe(bb); - throw new Problem(400, { detail: 'Malformed body: form-data expects \'file\' field' }); - } - - const filename = req.currentObject?.path.match(/(?!.*\/)(.*)$/)[0]; - const handleUpdateFile = () => { - const objId = addDashesToUuid(req.params.objectId); - data = { - id: objId, - bucketId: bucketId, - name: req.currentObject?.path.match(/(?!.*\/)(.*)$/)[0], - mimeType: info.mimeType, - metadata: getMetadata(req.headers), - tags: { - ...req.query.tagset, - 'coms-id': objId // Enforce `coms-id:` tag - } - }; - - // TODO: Consider adding in PutObject analog for sub 5GB file operations - const contentLength = parseInt(req.get('Content-Length')); - if (contentLength < MAXFILEOBJECTLENGTH) { - s3Promise = storageService.upload({ ...data, stream: stream }); - } else { - req.unpipe(bb); - throw new Problem(413, { detail: 'File exceeds maximum 50GB limit' }); - } - }; - - try { - // Preflight S3 Object check - const headResponse = await storageService.headObject({ - filePath: joinPath(bucketKey, filename), - bucketId: bucketId - }); - - if (headResponse.$metadata?.httpStatusCode === 200) handleUpdateFile(); - } catch (err) { - req.unpipe(bb); - if (err instanceof Problem) throw err; - else if (err.$metadata?.httpStatusCode !== 404) { - // Object does not exist on bucket - throw new Problem(502, { detail: 'Bucket communication error' }); - } else { - // Bucket is missing the existing object - throw new Problem(409, { detail: 'Bucket does not contain existing object' }); - // TODO: Add in sync operation to flush object out of COMS DB? - } - } - } catch (e) { - next(errorToProblem(SERVICE, e)); - } - }); - - bb.on('close', async () => { - try { - const s3Response = await s3Promise; - const dbResponse = await utils.trxWrapper(async (trx) => { - // Update Object - const object = await objectService.update({ - ...data, - userId: userId, - path: joinPath(bucketKey, data.name) - }, trx); - - // Update Version - let version = undefined; - if (s3Response.VersionId) { // Create new version if bucket versioning enabled - const s3VersionId = s3Response.VersionId; - version = await versionService.create({ - ...data, - etag: s3Response.ETag, - s3VersionId: s3VersionId, - isLatest: true - }, userId, trx); - } else { // Update existing version when bucket versioning not enabled - version = await versionService.update({ - ...data, - s3VersionId: null, - etag: s3Response.ETag - }, userId, trx); - } - object.versionId = version.id; - - // Update Metadata - if (data.metadata && Object.keys(data.metadata).length) await metadataService.associateMetadata(version.id, getKeyValue(data.metadata), userId, trx); - // TODO: if in unversioned bucket, dissociate old metadata. This is currently done in associateMetadata() with the global-style pruneOrphanedMetadata() method. - - // Update Tags - if (data.tags && Object.keys(data.tags).length) await tagService.replaceTags(version.id, getKeyValue(data.tags), userId, trx); - - return object; - }); - - res.status(200).json({ - ...data, - ...dbResponse, - ...renameObjectProperty(s3Response, 'VersionId', 's3VersionId') - }); - } catch (e) { - next(errorToProblem(SERVICE, e)); - } - }); - - bb.on('error', (e) => { - throw new Error(`Stream Error: ${e}`); // TODO: Revisit on error stack refactor - }); - - req.connection.on('error', (e) => { - throw new Error(`Connection Error: ${e}`); // TODO: Revisit on error stack refactor - }); - - req.pipe(bb); - } catch (e) { - next(errorToProblem(SERVICE, e)); - } } }; diff --git a/app/src/docs/v1.api-spec.yaml b/app/src/docs/v1.api-spec.yaml index 1c0bf977..b184c40d 100644 --- a/app/src/docs/v1.api-spec.yaml +++ b/app/src/docs/v1.api-spec.yaml @@ -319,70 +319,6 @@ paths: $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/Error" - post: - deprecated: true - summary: Creates an object - description: >- - Creates an object in the bucket specified with bucketId parameter or - default bucket configured. If COMS is running in either 'OIDC' or 'Full' - mode, any objects created with OIDC user authentication will have all - object permissions assigned to them by default. This endpoint will do a - best attempt effort to create the object as requested as long as it does - not already exist in the destination bucket and path. Existing objects - should be managed with the updateObject endpoint instead. - operationId: createObjectPost - tags: - - Object - parameters: - - $ref: "#/components/parameters/Header-Metadata" - - $ref: "#/components/parameters/Query-TagSet" - - in: query - name: bucketId - description: Uuid representing the bucket - schema: - type: string - format: uuid - example: ac246e31-c807-496c-bc93-cd8bc2f1b2b4 - requestBody: - description: Form-data containing files - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - description: >- - Contains the binary representation of the file to upload. - Maximum filesize of 5TB. - format: binary - maxLength: 5497558138880 - responses: - "200": - description: Returns the created object data - content: - application/json: - schema: - $ref: "#/components/schemas/Response-ObjectDetails" - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthorized" - "403": - $ref: "#/components/responses/Forbidden" - "409": - description: Conflict. Bucket already contains the object - content: - application/json: - schema: - $ref: "#/components/schemas/Response-Conflict" - "413": - $ref: "#/components/responses/ContentTooLarge" - "422": - $ref: "#/components/responses/UnprocessableEntity" - default: - $ref: "#/components/responses/Error" get: summary: Search for objects description: >- @@ -583,60 +519,6 @@ paths: $ref: "#/components/responses/UnprocessableEntity" default: $ref: "#/components/responses/Error" - post: - deprecated: true - summary: Updates an object - description: >- - Updates the object in the configured object storage. If the object - storage supports versioning, a new version will be generated instead of - overwriting the existing contents. - operationId: updateObjectPost - tags: - - Object - parameters: - - $ref: "#/components/parameters/Header-Metadata" - - $ref: "#/components/parameters/Path-ObjectId" - - $ref: "#/components/parameters/Query-TagSet" - requestBody: - description: Form-data containing files - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - description: >- - Contains the binary representation of the file to upload. - Maximum filesize of 5TB. - format: binary - maxLength: 5497558138880 - responses: - "200": - description: Returns the updated object data - content: - application/json: - schema: - $ref: "#/components/schemas/Response-ObjectDetails" - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthorized" - "403": - $ref: "#/components/responses/Forbidden" - "409": - description: Conflict. Bucket does not contain the existing object - content: - application/json: - schema: - $ref: "#/components/schemas/Response-Conflict" - "413": - $ref: "#/components/responses/ContentTooLarge" - "422": - $ref: "#/components/responses/UnprocessableEntity" - default: - $ref: "#/components/responses/Error" delete: summary: Deletes an object or a version of object description: >- diff --git a/app/src/middleware/authorization.js b/app/src/middleware/authorization.js index 54a62c43..b079573d 100644 --- a/app/src/middleware/authorization.js +++ b/app/src/middleware/authorization.js @@ -88,8 +88,6 @@ const currentObject = async (req, _res, next) => { if (req.params.objectId) { req.currentObject = Object.freeze({ ...await objectService.read(req.params.objectId) - // TODO: Determine if this is required or can be pushed down to controller level because this inflates all object related service call times by ~300ms - //...await storageService.listObjectVersion({ filePath: await getPath(req.params.objectId) }) }); } } catch (err) { diff --git a/app/src/routes/v1/object.js b/app/src/routes/v1/object.js index e4043768..a1943db6 100644 --- a/app/src/routes/v1/object.js +++ b/app/src/routes/v1/object.js @@ -14,14 +14,6 @@ router.put('/', objectValidator.createObject, requireSomeAuth, currentUpload(tru objectController.createObject(req, res, next); }); -/** - * Creates new objects - * @deprecated - */ -router.post('/', objectValidator.createObject, requireSomeAuth, (req, res, next) => { - objectController.createObjectPost(req, res, next); -}); - /** Search for objects */ router.get('/', objectValidator.searchObjects, requireSomeAuth, (req, res, next) => { objectController.searchObjects(req, res, next); @@ -53,14 +45,6 @@ router.put('/:objectId', objectValidator.updateObject, requireSomeAuth, currentU objectController.updateObject(req, res, next); }); -/** - * Updates an object - * @deprecated - */ -router.post('/:objectId', objectValidator.updateObject, requireSomeAuth, currentObject, hasPermission(Permissions.UPDATE), (req, res, next) => { - objectController.updateObjectPost(req, res, next); -}); - /** Deletes the object */ router.delete('/:objectId', objectValidator.deleteObject, requireSomeAuth, currentObject, hasPermission(Permissions.DELETE), (req, res, next) => { objectController.deleteObject(req, res, next); diff --git a/app/src/services/storage.js b/app/src/services/storage.js index 2899bbce..cb636ea3 100644 --- a/app/src/services/storage.js +++ b/app/src/services/storage.js @@ -8,7 +8,6 @@ const { GetObjectTaggingCommand, HeadBucketCommand, HeadObjectCommand, - ListObjectsCommand, ListObjectsV2Command, ListObjectVersionsCommand, PutObjectCommand, @@ -312,26 +311,6 @@ const objectStorageService = { return Promise.resolve({ DeleteMarkers: deleteMarkers, Versions: versions }); }, - /** - * @deprecated Use `listObjectsV2` instead - * @function listObjects - * Lists the objects in the bucket with the prefix of `filePath` - * @param {string} options.filePath The filePath of the object - * @param {number} [options.maxKeys] Optional maximum number of keys to return - * @param {string} [options.bucketId] Optional bucketId - * @returns {Promise} The response of the list objects operation - */ - async listObjects({ filePath, maxKeys = undefined, bucketId = undefined }) { - const data = await utils.getBucket(bucketId); - const params = { - Bucket: data.bucket, - Prefix: filePath, // Must filter via "prefix" - https://stackoverflow.com/a/56569856 - MaxKeys: maxKeys - }; - - return this._getS3Client(data).send(new ListObjectsCommand(params)); - }, - /** * @function listObjectsV2 * Lists the objects in the bucket with the prefix of `filePath` diff --git a/app/tests/common/dbHelper.js b/app/tests/common/dbHelper.js deleted file mode 100644 index 64ad945c..00000000 --- a/app/tests/common/dbHelper.js +++ /dev/null @@ -1,80 +0,0 @@ -let returnValue = undefined; - -const retFn = (value) => { - returnValue = value; -}; - -/** - * @class MockTransaction - * @deprecated Only use this as a shape reference - * Mock Objection Transaction - */ -class MockTransaction {} - -// Mocked Objection Functions -MockTransaction.commit = jest.fn().mockReturnThis(); -MockTransaction.rollback = jest.fn().mockReturnThis(); - -// Utility Functions -MockTransaction.mockClear = () => { - Object.keys(MockTransaction).forEach((f) => { - if (jest.isMockFunction(f)) f.mockClear(); - }); -}; -MockTransaction.mockReset = () => { - Object.keys(MockTransaction).forEach((f) => { - if (jest.isMockFunction(f)) f.mockReset(); - }); -}; - -/** - * @class MockModel - * @deprecated Only use this as a shape reference - * Mock Objection Model - */ -class MockModel {} - -// Mocked Objection Functions -MockModel.findById = jest.fn().mockReturnThis(); -MockModel.first = jest.fn().mockReturnThis(); -MockModel.delete = jest.fn().mockReturnThis(); -MockModel.deleteById = jest.fn().mockReturnThis(); -MockModel.insert = jest.fn().mockReturnThis(); -MockModel.insertAndFetch = jest.fn().mockReturnThis(); -MockModel.insertGraph = jest.fn().mockReturnThis(); -MockModel.insertGraphAndFetch = jest.fn().mockReturnThis(); -MockModel.findById = jest.fn().mockReturnThis(); -MockModel.modify = jest.fn().mockReturnThis(); -MockModel.modifiers = jest.fn().mockReturnThis(); -MockModel.orderBy = jest.fn().mockReturnThis(); -MockModel.patchAndFetchById = jest.fn().mockReturnThis(); -MockModel.query = jest.fn().mockReturnThis(); -MockModel.resolve = jest.fn().mockResolvedValue(returnValue); -MockModel.returning = jest.fn().mockReturnThis(); -MockModel.select = jest.fn().mockReturnThis(); -MockModel.skipUndefined = jest.fn(() => { - throw new Error('skipUndefined() is deprecated in Objection 3.0. Refactor to not use this method!'); -}), -MockModel.startTransaction = jest.fn().mockResolvedValue(MockTransaction); -MockModel.then = jest.fn((done) => { done(returnValue); }); -MockModel.throwIfNotFound = jest.fn().mockReturnThis(); -MockModel.where = jest.fn().mockReturnThis(); -MockModel.withGraphFetched = jest.fn().mockReturnThis(); - -// Utility Functions -MockModel.mockClear = () => { - Object.keys(MockModel).forEach((f) => { - if (jest.isMockFunction(f)) f.mockClear(); - }); - returnValue = undefined; -}; -MockModel.mockReset = () => { - Object.keys(MockModel).forEach((f) => { - if (jest.isMockFunction(f)) f.mockReset(); - }); - returnValue = undefined; -}; -MockModel.mockResolvedValue = retFn; -MockModel.mockReturnValue = retFn; - -module.exports = { MockModel, MockTransaction }; diff --git a/app/tests/unit/components/utils.spec.js b/app/tests/unit/components/utils.spec.js index 7dfa9393..62df1e21 100644 --- a/app/tests/unit/components/utils.spec.js +++ b/app/tests/unit/components/utils.spec.js @@ -1,7 +1,7 @@ const config = require('config'); const { AuthMode, AuthType } = require('../../../src/components/constants'); -const { bucketService, objectService } = require('../../../src/services'); +const { bucketService } = require('../../../src/services'); const utils = require('../../../src/components/utils'); const Problem = require('api-problem'); @@ -53,53 +53,6 @@ describe('calculatePartSize', () => { }); }); -// TODO: Deprecated, to remove this -describe('getPath', () => { - const delimitSpy = jest.spyOn(utils, 'delimit'); - const joinPath = jest.spyOn(utils, 'joinPath'); - const getBucketKey = jest.spyOn(objectService, 'getBucketKey'); - - const key = 'abc'; - const osKey = 'key'; - const value = 'abc/obj'; - - it('should return a valid path', async () => { - delimitSpy.mockReturnValue('wrong'); - joinPath.mockReturnValue(value); - getBucketKey.mockResolvedValue({ key: key }); - config.get.mockReturnValueOnce(osKey); // objectStorage.key - - const result = await utils.getPath('obj'); - - expect(result).toEqual(value); - - expect(delimitSpy).toHaveBeenCalledTimes(1); - expect(delimitSpy).toHaveBeenCalledWith(osKey); - expect(joinPath).toHaveBeenCalledTimes(1); - expect(joinPath).toHaveBeenCalledWith(key, 'obj'); - expect(getBucketKey).toHaveBeenCalledTimes(1); - expect(getBucketKey).toHaveBeenCalledWith('obj'); - }); - - it('should return a valid path', async () => { - delimitSpy.mockReturnValue(key); - joinPath.mockReturnValue(value); - getBucketKey.mockImplementation(() => { throw new Error(); }); - config.get.mockReturnValueOnce(osKey); // objectStorage.key - - const result = await utils.getPath('obj'); - - expect(result).toEqual(value); - - expect(delimitSpy).toHaveBeenCalledTimes(1); - expect(delimitSpy).toHaveBeenCalledWith(osKey); - expect(joinPath).toHaveBeenCalledTimes(1); - expect(joinPath).toHaveBeenCalledWith(key, 'obj'); - expect(getBucketKey).toHaveBeenCalledTimes(1); - expect(getBucketKey).toHaveBeenCalledWith('obj'); - }); -}); - describe('delimit', () => { beforeAll(() => { if (jest.isMockFunction(utils.delimit)) { diff --git a/app/tests/unit/middleware/authorization.spec.js b/app/tests/unit/middleware/authorization.spec.js index e831cc23..8853f887 100644 --- a/app/tests/unit/middleware/authorization.spec.js +++ b/app/tests/unit/middleware/authorization.spec.js @@ -197,8 +197,6 @@ describe('currentObject', () => { expect(req.currentObject).toBeUndefined(); expect(objectReadSpy).toHaveBeenCalledTimes(0); - // expect(storageListObjectVersionSpy).toHaveBeenCalledTimes(0); - expect(utils.getPath).toHaveBeenCalledTimes(0); expect(next).toHaveBeenCalledTimes(1); expect(next).toHaveBeenCalledWith(); }); @@ -214,8 +212,6 @@ describe('currentObject', () => { expect(req.currentObject).toBeUndefined(); expect(objectReadSpy).toHaveBeenCalledTimes(1); expect(objectReadSpy).toHaveBeenCalledWith(objectId); - // expect(storageListObjectVersionSpy).toHaveBeenCalledTimes(0); - expect(utils.getPath).toHaveBeenCalledTimes(0); expect(next).toHaveBeenCalledTimes(1); expect(next).toHaveBeenCalledWith(); }); @@ -226,8 +222,6 @@ describe('currentObject', () => { // const testStorage = { b: 2 }; req.params = { objectId: objectId }; objectReadSpy.mockResolvedValue(testRecord); - // storageListObjectVersionSpy.mockResolvedValue(testStorage); - // utils.getPath.mockReturnValue(`/path/${objectId}`); await mw.currentObject(req, res, next); @@ -236,12 +230,6 @@ describe('currentObject', () => { // expect(req.currentObject).toEqual(expect.objectContaining(testStorage)); expect(objectReadSpy).toHaveBeenCalledTimes(1); expect(objectReadSpy).toHaveBeenCalledWith(objectId); - // expect(storageListObjectVersionSpy).toHaveBeenCalledTimes(1); - // expect(storageListObjectVersionSpy).toHaveBeenCalledWith({ - // filePath: expect.stringMatching(`/path/${objectId}`) - // }); - // expect(utils.getPath).toHaveBeenCalledTimes(1); - // expect(utils.getPath).toHaveBeenCalledWith(objectId); expect(next).toHaveBeenCalledTimes(1); expect(next).toHaveBeenCalledWith(); }); diff --git a/app/tests/unit/routes/v1/object.spec.js b/app/tests/unit/routes/v1/object.spec.js index 08bc5f55..c8ead71c 100644 --- a/app/tests/unit/routes/v1/object.spec.js +++ b/app/tests/unit/routes/v1/object.spec.js @@ -12,9 +12,8 @@ const app = expressHelper(basePath, router); // Mock config library - @see {@link https://stackoverflow.com/a/64819698} jest.mock('config'); -// TODO: Repoint to PUT endpoint -describe(`POST ${basePath}`, () => { - const spy = jest.spyOn(objectController, 'createObjectPost'); +describe(`GET ${basePath}`, () => { + const spy = jest.spyOn(objectController, 'searchObjects'); beforeEach(() => { spy.mockReset(); @@ -22,8 +21,8 @@ describe(`POST ${basePath}`, () => { it('Should call controller', async () => { // eslint-disable-next-line no-unused-vars - spy.mockImplementation((req, res, next) => res.status(200).end()); - await request(app).post(`${basePath}`); + spy.mockImplementation((_req, res, _next) => res.status(200).end()); + await request(app).get(`${basePath}`); expect(spy).toHaveBeenCalledTimes(1); }); diff --git a/app/tests/unit/services/storage.spec.js b/app/tests/unit/services/storage.spec.js index 40e9c068..9c8ed137 100644 --- a/app/tests/unit/services/storage.spec.js +++ b/app/tests/unit/services/storage.spec.js @@ -8,7 +8,6 @@ const { GetObjectTaggingCommand, HeadBucketCommand, HeadObjectCommand, - ListObjectsCommand, ListObjectsV2Command, ListObjectVersionsCommand, PutBucketEncryptionCommand, @@ -676,41 +675,6 @@ describe('listAllObjectVersions', () => { }); }); -describe('listObjects', () => { - beforeEach(() => { - s3ClientMock.on(ListObjectsCommand).resolves({}); - }); - - it('should send a list objects command with default undefined maxKeys', async () => { - const filePath = 'filePath'; - const result = await service.listObjects({ filePath }); - - expect(result).toBeTruthy(); - expect(utils.getBucket).toHaveBeenCalledTimes(1); - expect(s3ClientMock).toHaveReceivedCommandTimes(ListObjectsCommand, 1); - expect(s3ClientMock).toHaveReceivedCommandWith(ListObjectsCommand, { - Bucket: bucket, - Prefix: filePath, - MaxKeys: undefined - }); - }); - - it('should send a list objects command with 200 maxKeys', async () => { - const filePath = 'filePath'; - const maxKeys = 200; - const result = await service.listObjects({ filePath, maxKeys }); - - expect(result).toBeTruthy(); - expect(utils.getBucket).toHaveBeenCalledTimes(1); - expect(s3ClientMock).toHaveReceivedCommandTimes(ListObjectsCommand, 1); - expect(s3ClientMock).toHaveReceivedCommandWith(ListObjectsCommand, { - Bucket: bucket, - Prefix: filePath, - MaxKeys: maxKeys - }); - }); -}); - describe('listObjectsV2', () => { beforeEach(() => { s3ClientMock.on(ListObjectsV2Command).resolves({}); @@ -823,20 +787,14 @@ describe('putBucketEncryption', () => { }); describe('putObject', () => { - const getPathSpy = jest.spyOn(utils, 'getPath'); const name = 'foo.txt'; const keyPath = utils.joinPath(key, name); const length = 123; beforeEach(() => { - getPathSpy.mockResolvedValue(keyPath); s3ClientMock.on(PutObjectCommand).resolves({}); }); - afterAll(() => { - getPathSpy.mockRestore(); - }); - it('should send a put object command', async () => { const stream = new Readable(); const mimeType = 'mimeType'; @@ -1038,12 +996,9 @@ describe('readSignedUrl', () => { }); describe('upload', () => { - const getPathSpy = jest.spyOn(utils, 'getPath'); const id = 'id'; - const keyPath = utils.joinPath(key, id); beforeEach(() => { - getPathSpy.mockResolvedValue(keyPath); s3ClientMock.on(PutObjectCommand).resolves({}); s3ClientMock.on(PutObjectTaggingCommand).resolves({}); });