Skip to content

Commit

Permalink
feat(s3): support copy/delete/get/head object APIs (#11515)
Browse files Browse the repository at this point in the history
* fix(s3): add URL polyfill for react-native
  • Loading branch information
AllanZhengYP authored Jun 19, 2023
1 parent 835b74f commit 3e2c1a9
Show file tree
Hide file tree
Showing 15 changed files with 2,066 additions and 182 deletions.
66 changes: 66 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/copyObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { copyObject } from '../../../../src/AwsClients/S3';
import { ApiFunctionalTestCase } from '../../testUtils/types';
import {
defaultConfig,
DEFAULT_RESPONSE_HEADERS,
expectedMetadata,
} from './shared';

// API Reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
const copyObjectHappyCase: ApiFunctionalTestCase<typeof copyObject> = [
'happy case',
'copyObject',
copyObject,
defaultConfig,
{
Bucket: 'bucket',
CopySource: 'sourceBucket/sourceKey', // TODO: test with encoded source key
Key: 'key',
CacheControl: 'cacheControl',
ContentType: 'contentType',
ACL: 'acl',
ServerSideEncryption: 'serverSideEncryption',
SSECustomerAlgorithm: 'sseCustomerAlgorithm',
SSECustomerKey: 'sseCustomerKey',
SSECustomerKeyMD5: 'sseCustomerKeyMD5',
SSEKMSKeyId: 'sseKMSKeyId',
},
expect.objectContaining({
url: expect.objectContaining({
href: 'https://bucket.s3.us-east-1.amazonaws.com/key',
}),
method: 'PUT',
headers: expect.objectContaining({
'x-amz-copy-source': 'sourceBucket/sourceKey',
'cache-control': 'cacheControl',
'content-type': 'contentType',
'x-amz-acl': 'acl',
'x-amz-server-side-encryption': 'serverSideEncryption',
'x-amz-server-side-encryption-customer-algorithm': 'sseCustomerAlgorithm',
'x-amz-server-side-encryption-customer-key': 'sseCustomerKey',
'x-amz-server-side-encryption-customer-key-md5': 'sseCustomerKeyMD5',
'x-amz-server-side-encryption-aws-kms-key-id': 'sseKMSKeyId',
}),
}),
{
status: 200,
headers: DEFAULT_RESPONSE_HEADERS,
body:
'<CopyObjectResult>' +
'<ETag>string</ETag>' +
'<LastModified>timestamp</LastModified>' +
'<ChecksumCRC32>string</ChecksumCRC32>' +
'<ChecksumCRC32C>string</ChecksumCRC32C>' +
'<ChecksumSHA1>string</ChecksumSHA1>' +
'<ChecksumSHA256>string</ChecksumSHA256>' +
'</CopyObjectResult>',
},
{
$metadata: expect.objectContaining(expectedMetadata),
},
];

export default [copyObjectHappyCase];
38 changes: 38 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/deleteObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { deleteObject } from '../../../../src/AwsClients/S3';
import { ApiFunctionalTestCase } from '../../testUtils/types';
import {
defaultConfig,
DEFAULT_RESPONSE_HEADERS,
expectedMetadata,
} from './shared';

// API Reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
const deleteObjectHappyCase: ApiFunctionalTestCase<typeof deleteObject> = [
'happy case',
'deleteObject',
deleteObject,
defaultConfig,
{
Bucket: 'bucket',
Key: 'key',
},
expect.objectContaining({
url: expect.objectContaining({
href: 'https://bucket.s3.us-east-1.amazonaws.com/key',
}),
method: 'DELETE',
}),
{
status: 200,
headers: DEFAULT_RESPONSE_HEADERS,
body: '',
},
{
$metadata: expect.objectContaining(expectedMetadata),
},
];

export default [deleteObjectHappyCase];
184 changes: 184 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/getObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { getObject } from '../../../../src/AwsClients/S3';
import { ApiFunctionalTestCase } from '../../testUtils/types';
import {
defaultConfig,
DEFAULT_RESPONSE_HEADERS,
expectedMetadata,
EMPTY_SHA256,
} from './shared';

const getObjectResponseHeaders = {
'x-amz-delete-marker': 'true',
'accept-ranges': 'types',
'x-amz-expiration':
'expiry-date="Fri, 23 Dec 2012 00:00:00 GMT", rule-id="picture-deletion-rule"',
'x-amz-restore':
'ongoing-request="false", expiry-date="Fri, 21 Dec 2012 00:00:00 GMT"', // Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#AmazonS3-HeadObject-response-header-Restore
'last-modified': 'Sun, 1 Jan 2006 12:00:00 GMT',
'content-length': '434234',
etag: 'fba9dede5f27731c9771645a39863328',
'x-amz-checksum-crc32': '696e1637',
'x-amz-checksum-crc32c': '028A5A90',
'x-amz-checksum-sha1': '5f3446f6cb2f4962082dfe2d298d1b1a32a21b21',
'x-amz-checksum-sha256':
'1643577c036c1e057505b4dce59f3d34bd3fe6224f1064c80dd5426b27a12360',
'x-amz-missing-meta': '2',
'x-amz-version-id': '3HL4kqtJlcpXroDTDmjVBH40Nrjfkd',
'cache-control': 'no-store',
'content-disposition': 'attachment',
'content-encoding': 'zip',
'content-language': 'en-US',
'content-range': 'bytes 0-9/443',
'content-type': 'text/plain',
expires: 'Thu, 01 Dec 1994 16:00:00 GMT',
'x-amz-website-redirect-location': 'http://www.example.com/',
'x-amz-server-side-encryption': 'aws:kms',
'x-amz-server-side-encryption-customer-algorithm': 'AES256',
'x-amz-server-side-encryption-customer-key-md5': 'ZjQrne1X/iTcskbY2m3example',
'x-amz-server-side-encryption-aws-kms-key-id': '12345keyId',
'x-amz-server-side-encryption-bucket-key-enabled': 'true',
'x-amz-storage-class': 'STANDARD',
'x-amz-request-charged': 'requester',
'x-amz-replication-status': 'COMPLETE',
'x-amz-mp-parts-count': '100',
'x-amz-tagging-count': '100',
'x-amz-object-lock-mode': 'COMPLIANCE',
'x-amz-object-lock-retain-until-date': 'Fri, 23 Dec 2012 00:00:00 GMT',
'x-amz-object-lock-legal-hold': 'ON',
// metadata
'x-amz-meta-param1': 'value 1',
'x-amz-meta-param2': 'value 2',
} as const;

// API Reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
const getObjectHappyCase: ApiFunctionalTestCase<typeof getObject> = [
'happy case',
'getObject',
getObject,
defaultConfig,
{
Bucket: 'bucket',
Key: 'key',
ResponseCacheControl: 'ResponseCacheControl',
ResponseContentDisposition: 'ResponseContentDisposition',
ResponseContentEncoding: 'ResponseContentEncoding',
ResponseContentLanguage: 'ResponseContentLanguage',
ResponseContentType: 'ResponseContentType',
SSECustomerAlgorithm: 'SSECustomerAlgorithm',
SSECustomerKey: 'SSECustomerKey',
SSECustomerKeyMD5: 'SSECustomerKeyMD5',
},
expect.objectContaining({
url: expect.objectContaining({
href: 'https://bucket.s3.us-east-1.amazonaws.com/key?response-cache-control=ResponseCacheControl&response-content-disposition=ResponseContentDisposition&response-content-encoding=ResponseContentEncoding&response-content-language=ResponseContentLanguage&response-content-type=ResponseContentType',
}),
method: 'GET',
headers: expect.objectContaining({
authorization: expect.stringContaining('Signature'),
host: 'bucket.s3.us-east-1.amazonaws.com',
'x-amz-server-side-encryption-customer-algorithm': 'SSECustomerAlgorithm',
'x-amz-server-side-encryption-customer-key': 'SSECustomerKey',
'x-amz-server-side-encryption-customer-key-md5': 'SSECustomerKeyMD5',
'x-amz-content-sha256': EMPTY_SHA256,
'x-amz-date': expect.stringMatching(/^\d{8}T\d{6}Z/),
'x-amz-user-agent': expect.stringContaining('aws-amplify'),
}),
}),
{
status: 200,
// all header names are already lowercased by transfer handlers
headers: {
...DEFAULT_RESPONSE_HEADERS,
...getObjectResponseHeaders,
},
body: 'mockBody',
},
{
DeleteMarker: true,
AcceptRanges: 'types',
Expiration: getObjectResponseHeaders['x-amz-expiration'],
Restore: getObjectResponseHeaders['x-amz-restore'],
LastModified: new Date(getObjectResponseHeaders['last-modified']),
ContentLength: Number(getObjectResponseHeaders['content-length']),
ETag: getObjectResponseHeaders.etag,
ChecksumCRC32: getObjectResponseHeaders['x-amz-checksum-crc32'],
ChecksumCRC32C: getObjectResponseHeaders['x-amz-checksum-crc32c'],
ChecksumSHA1: getObjectResponseHeaders['x-amz-checksum-sha1'],
ChecksumSHA256: getObjectResponseHeaders['x-amz-checksum-sha256'],
MissingMeta: Number(getObjectResponseHeaders['x-amz-missing-meta']),
VersionId: getObjectResponseHeaders['x-amz-version-id'],
CacheControl: getObjectResponseHeaders['cache-control'],
ContentDisposition: getObjectResponseHeaders['content-disposition'],
ContentEncoding: getObjectResponseHeaders['content-encoding'],
ContentLanguage: getObjectResponseHeaders['content-language'],
ContentRange: getObjectResponseHeaders['content-range'],
ContentType: getObjectResponseHeaders['content-type'],
Expires: new Date(getObjectResponseHeaders.expires),
WebsiteRedirectLocation:
getObjectResponseHeaders['x-amz-website-redirect-location'],
ServerSideEncryption:
getObjectResponseHeaders['x-amz-server-side-encryption'],
SSECustomerAlgorithm:
getObjectResponseHeaders[
'x-amz-server-side-encryption-customer-algorithm'
],
SSECustomerKeyMD5:
getObjectResponseHeaders['x-amz-server-side-encryption-customer-key-md5'],
SSEKMSKeyId:
getObjectResponseHeaders['x-amz-server-side-encryption-aws-kms-key-id'],
BucketKeyEnabled: true,
StorageClass: getObjectResponseHeaders['x-amz-storage-class'],
RequestCharged: getObjectResponseHeaders['x-amz-request-charged'],
ReplicationStatus: getObjectResponseHeaders['x-amz-replication-status'],
PartsCount: Number(getObjectResponseHeaders['x-amz-mp-parts-count']),
TagCount: Number(getObjectResponseHeaders['x-amz-tagging-count']),
ObjectLockMode: getObjectResponseHeaders['x-amz-object-lock-mode'],
ObjectLockRetainUntilDate: new Date(
getObjectResponseHeaders['x-amz-object-lock-retain-until-date']
),
ObjectLockLegalHoldStatus:
getObjectResponseHeaders['x-amz-object-lock-legal-hold'],
Metadata: {
param1: 'value 1',
param2: 'value 2',
},
Body: expect.objectContaining({
text: expect.any(Function),
blob: expect.any(Function),
json: expect.any(Function),
}),
$metadata: expect.objectContaining(expectedMetadata),
},
];

const getObjectAccelerateEndpoint: ApiFunctionalTestCase<typeof getObject> = [
'happy case',
'getObject with accelerate endpoint',
getObject,
{
...defaultConfig,
useAccelerateEndpoint: true,
} as Parameters<typeof getObject>[0],
{
Bucket: 'bucket',
Key: 'key',
},
expect.objectContaining({
url: expect.objectContaining({
href: 'https://bucket.s3-accelerate.amazonaws.com/key',
}),
}),
{
status: 200,
headers: DEFAULT_RESPONSE_HEADERS,
body: 'mockBody',
},
expect.objectContaining({
/** skip validating response */
}) as any,
];

export default [getObjectHappyCase, getObjectAccelerateEndpoint];
58 changes: 58 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/headObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { headObject } from '../../../../src/AwsClients/S3';
import { ApiFunctionalTestCase } from '../../testUtils/types';
import {
defaultConfig,
DEFAULT_RESPONSE_HEADERS,
expectedMetadata,
} from './shared';

// API Reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html
const headObjectHappyCase: ApiFunctionalTestCase<typeof headObject> = [
'happy case',
'headObject',
headObject,
defaultConfig,
{
Bucket: 'bucket',
Key: 'key',
SSECustomerAlgorithm: 'sseCustomerAlgorithm',
SSECustomerKey: 'sseCustomerKey',
SSECustomerKeyMD5: 'sseCustomerKeyMD5',
},
expect.objectContaining({
url: expect.objectContaining({
href: 'https://bucket.s3.us-east-1.amazonaws.com/key',
}),
method: 'HEAD',
headers: expect.objectContaining({
'x-amz-server-side-encryption-customer-algorithm': 'sseCustomerAlgorithm',
'x-amz-server-side-encryption-customer-key': 'sseCustomerKey',
'x-amz-server-side-encryption-customer-key-md5': 'sseCustomerKeyMD5',
}),
}),
{
status: 200,
headers: {
...DEFAULT_RESPONSE_HEADERS,
'content-length': '434234',
'content-type': 'text/plain',
etag: 'etag',
'last-modified': 'Sun, 1 Jan 2006 12:00:00 GMT',
'x-amz-version-id': 'versionId',
},
body: '',
},
{
$metadata: expect.objectContaining(expectedMetadata),
ContentLength: 434234,
ContentType: 'text/plain',
ETag: 'etag',
LastModified: new Date('Sun, 1 Jan 2006 12:00:00 GMT'),
VersionId: 'versionId',
},
];

export default [headObjectHappyCase];
8 changes: 8 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import uploadPartCases from './uploadPart';
import completeMultipartUploadCases from './completeMultipartUpload';
import abortMultipartUploadCases from './abortMultipartUpload';
import listPartsCases from './listParts';
import copyObjectCases from './copyObject';
import deleteObjectCases from './deleteObject';
import getObjectCases from './getObject';
import headObjectCases from './headObject';

export default [
...listObjectsV2Cases,
Expand All @@ -17,4 +21,8 @@ export default [
...completeMultipartUploadCases,
...abortMultipartUploadCases,
...listPartsCases,
...copyObjectCases,
...deleteObjectCases,
...getObjectCases,
...headObjectCases,
];
Loading

0 comments on commit 3e2c1a9

Please sign in to comment.