From c0b5e6d6e26a0e717cdf79236a9bfe1a62cdbf54 Mon Sep 17 00:00:00 2001 From: dorianfouvez Date: Wed, 21 May 2025 11:10:23 +0200 Subject: [PATCH 1/2] feat(copy): add the copy method to the S3Handler --- README.md | 5 ++++- src/S3Handler.ts | 16 +++++++++++++ test/src/copyObject.ts | 51 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/src/copyObject.ts diff --git a/README.md b/README.md index e994dfd..2f1b8c6 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,10 @@ await s3Handler.listObjects('my-prefix'); await s3Handler.deleteObject('my-key'); // Delete several objects -await s3Handler.deleteObjects([{ Key: 'my-key1' }, {Key: 'my-key2'}]); +await s3Handler.deleteObjects([{ Key: 'my-key1' }, { Key: 'my-key2' }]); + +// Copy object +await s3Handler.copyObject('bucket/my-key', 'my-new-key'); ``` ### Readable Stream Usage diff --git a/src/S3Handler.ts b/src/S3Handler.ts index 9b6763b..c05db19 100644 --- a/src/S3Handler.ts +++ b/src/S3Handler.ts @@ -1,4 +1,6 @@ import { + CopyObjectCommand, + CopyObjectCommandInput, DeleteObjectCommand, DeleteObjectCommandInput, DeleteObjectsCommand, @@ -146,6 +148,20 @@ export class S3Handler { return this.client.send(deleteObjectsCommand); } + async copyObject( + sourceKey: string, + destinationKey: string, + options: Omit = {} + ) { + const copyObjectCommand = new CopyObjectCommand({ + Bucket: this.bucket, + Key: destinationKey, + CopySource: sourceKey, + ...options + }); + return this.client.send(copyObjectCommand); + } + async generateGetPresignedUrl( key: string, signedUrlOptions: RequestPresigningArguments = {}, diff --git a/test/src/copyObject.ts b/test/src/copyObject.ts new file mode 100644 index 0000000..0ab8a76 --- /dev/null +++ b/test/src/copyObject.ts @@ -0,0 +1,51 @@ +import { expect } from 'chai'; +import { CopyObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { mockClient } from 'aws-sdk-client-mock'; +import { S3Handler } from '../../src/S3Handler'; + +describe('S3Handler.copyObject', () => { + const s3ClientMock = mockClient(S3Client); + + beforeEach(() => { + s3ClientMock.reset(); + }); + + it('should copy an object', async () => { + s3ClientMock.on(CopyObjectCommand).resolvesOnce({}); + + const s3Handler = new S3Handler(new S3Client({}), 'my-dummy-bucket'); + + await s3Handler.copyObject('my-dummy-bucket/my-key', 'my-new-key'); + + expect(s3ClientMock.commandCalls(CopyObjectCommand).length).equal(1); + expect(s3ClientMock.commandCalls(CopyObjectCommand)[0].args[0].input).deep.equal({ + Bucket: 'my-dummy-bucket', + Key: 'my-new-key', + CopySource: `my-dummy-bucket/my-key` + }); + }); + + it('should copy an object with additional options', async () => { + s3ClientMock.on(CopyObjectCommand).resolvesOnce({}); + + const s3Handler = new S3Handler(new S3Client({}), 'my-dummy-bucket'); + + await s3Handler.copyObject('my-dummy-bucket/my-key', 'my-new-key', { + ContentType: 'text/plain', + ACL: 'public-read', + Metadata: { myCustomId: '1' }, + MetadataDirective: 'COPY' + }); + + expect(s3ClientMock.commandCalls(CopyObjectCommand).length).equal(1); + expect(s3ClientMock.commandCalls(CopyObjectCommand)[0].args[0].input).deep.equal({ + Bucket: 'my-dummy-bucket', + Key: 'my-new-key', + CopySource: `my-dummy-bucket/my-key`, + ContentType: 'text/plain', + ACL: 'public-read', + Metadata: { myCustomId: '1' }, + MetadataDirective: 'COPY' + }); + }); +}); From 8914cde926f5517fffe709b89dba60c3db91d4c2 Mon Sep 17 00:00:00 2001 From: dorianfouvez Date: Wed, 21 May 2025 11:24:48 +0200 Subject: [PATCH 2/2] chore(read-me): fix some errors --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2f1b8c6..1813dd8 100644 --- a/README.md +++ b/README.md @@ -97,28 +97,28 @@ npm run format npm test ``` +_Note: that's the one you want to use most of the time_ + ### Running lint tests ```sh -npm test:lint +npm run test:lint ``` ### Running coverage tests ```sh -npm test:cover +npm run test:cover ``` This will create a coverage folder with all the report in `coverage/index.html` -### Running all tests +### Running types tests ```sh -npm test:all +npm run test:types ``` -_Note: that's the one you want to use most of the time_ - ## Reporting bugs and contributing If you want to report a bug or request a feature, please open an issue.