Skip to content

Commit

Permalink
chore: durability checks for create & complete multipart
Browse files Browse the repository at this point in the history
  • Loading branch information
scyrizales-amz committed Sep 17, 2024
1 parent 7876916 commit c018013
Show file tree
Hide file tree
Showing 14 changed files with 620 additions and 57 deletions.
2 changes: 1 addition & 1 deletion packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@
"name": "[Storage] uploadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ uploadData }",
"limit": "21.83 kB"
"limit": "22.01 kB"
}
]
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { HttpResponse } from '@aws-amplify/core/internals/aws-client-utils';

import { s3TransferHandler } from '../../../../../../src/providers/s3/utils/client/runtime/s3TransferHandler/fetch';
import { completeMultipartUpload } from '../../../../../../src/providers/s3/utils/client/s3data';
import { validateObjectUrl } from '../../../../../../src/providers/s3/utils/validateObjectUrl';
import { validateMultipartUploadXML } from '../../../../../../src/providers/s3/utils/validateMultipartUploadXML';
import {
DEFAULT_RESPONSE_HEADERS,
defaultConfig,
expectedMetadata,
} from '../S3/cases/shared';
import { IntegrityError } from '../../../../../../src/errors/IntegrityError';

jest.mock('../../../../../../src/providers/s3/utils/validateObjectUrl');
jest.mock(
'../../../../../../src/providers/s3/utils/validateMultipartUploadXML',
);
jest.mock(
'../../../../../../src/providers/s3/utils/client/runtime/s3TransferHandler/fetch',
);

const mockS3TransferHandler = s3TransferHandler as jest.Mock;
const mockBinaryResponse = ({
status,
headers,
body,
}: {
status: number;
headers: Record<string, string>;
body: string;
}): HttpResponse => {
const responseBody = {
json: async (): Promise<any> => {
throw new Error(
'Parsing response to JSON is not implemented. Please use response.text() instead.',
);
},
blob: async () => new Blob([body], { type: 'plain/text' }),
text: async () => body,
} as HttpResponse['body'];

return {
statusCode: status,
headers,
body: responseBody,
} as any;
};

const completeMultipartUploadSuccessResponse = {
status: 200,
headers: {
...DEFAULT_RESPONSE_HEADERS,
'x-amz-version-id': 'versionId',
etag: 'etag',
},
body: '',
};

describe('completeMultipartUploadSerializer', () => {
const mockValidateObjectUrl = jest.mocked(validateObjectUrl);
const mockValidateMultipartUploadXML = jest.mocked(
validateMultipartUploadXML,
);
beforeEach(() => {
mockS3TransferHandler.mockReset();
});

it('should pass when objectUrl and multipartUploadXML is durable', async () => {
expect.assertions(1);
mockS3TransferHandler.mockResolvedValue(
mockBinaryResponse(completeMultipartUploadSuccessResponse as any),
);
const output = await completeMultipartUpload(defaultConfig, {
Bucket: 'bucket',
Key: 'key',
UploadId: 'uploadId',
MultipartUpload: {
Parts: [
{
ETag: 'etag',
PartNumber: 1,
},
],
},
});
console.log(output);
expect(output).toEqual({
$metadata: expect.objectContaining(expectedMetadata),
});
});

it('should fail when objectUrl is NOT durable', async () => {
expect.assertions(1);
mockS3TransferHandler.mockResolvedValue(
mockBinaryResponse(completeMultipartUploadSuccessResponse as any),
);
const integrityError = new IntegrityError();
mockValidateObjectUrl.mockImplementationOnce(() => {
throw integrityError;
});
expect(
completeMultipartUpload(defaultConfig, {
Bucket: 'bucket',
Key: 'key',
UploadId: 'uploadId',
MultipartUpload: {
Parts: [
{
ETag: 'etag',
PartNumber: 1,
},
],
},
}),
).rejects.toThrow(integrityError);
});

it('should fail when multipartUploadXML is NOT durable', async () => {
expect.assertions(1);
mockS3TransferHandler.mockResolvedValue(
mockBinaryResponse(completeMultipartUploadSuccessResponse as any),
);
const integrityError = new IntegrityError();
mockValidateMultipartUploadXML.mockImplementationOnce(() => {
throw integrityError;
});
expect(
completeMultipartUpload(defaultConfig, {
Bucket: 'bucket',
Key: 'key',
UploadId: 'uploadId',
MultipartUpload: {
Parts: [
{
ETag: 'etag',
PartNumber: 1,
},
],
},
}),
).rejects.toThrow(integrityError);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { HttpResponse } from '@aws-amplify/core/internals/aws-client-utils';

import { s3TransferHandler } from '../../../../../../src/providers/s3/utils/client/runtime/s3TransferHandler/fetch';
import { createMultipartUpload } from '../../../../../../src/providers/s3/utils/client/s3data';
import { validateObjectUrl } from '../../../../../../src/providers/s3/utils/validateObjectUrl';
import {
DEFAULT_RESPONSE_HEADERS,
defaultConfig,
expectedMetadata,
} from '../S3/cases/shared';
import { IntegrityError } from '../../../../../../src/errors/IntegrityError';

jest.mock('../../../../../../src/providers/s3/utils/validateObjectUrl');
jest.mock(
'../../../../../../src/providers/s3/utils/client/runtime/s3TransferHandler/fetch',
);

const mockS3TransferHandler = s3TransferHandler as jest.Mock;
const mockBinaryResponse = ({
status,
headers,
body,
}: {
status: number;
headers: Record<string, string>;
body: string;
}): HttpResponse => {
const responseBody = {
json: async (): Promise<any> => {
throw new Error(
'Parsing response to JSON is not implemented. Please use response.text() instead.',
);
},
blob: async () => new Blob([body], { type: 'plain/text' }),
text: async () => body,
} as HttpResponse['body'];

return {
statusCode: status,
headers,
body: responseBody,
} as any;
};

const createMultipartUploadSuccessResponse = {
status: 200,
headers: {
...DEFAULT_RESPONSE_HEADERS,
'x-amz-version-id': 'versionId',
etag: 'etag',
},
body: '',
};

describe('createMultipartUploadSerializer', () => {
const mockIsValidObjectUrl = jest.mocked(validateObjectUrl);
beforeEach(() => {
mockS3TransferHandler.mockReset();
});

it('should pass when objectUrl is durable', async () => {
expect.assertions(1);
mockS3TransferHandler.mockResolvedValue(
mockBinaryResponse(createMultipartUploadSuccessResponse as any),
);
const output = await createMultipartUpload(defaultConfig, {
Bucket: 'bucket',
Key: 'key',
});
console.log(output);
expect(output).toEqual({
$metadata: expect.objectContaining(expectedMetadata),
});
});

it('should fail when objectUrl is NOT durable', async () => {
expect.assertions(1);
mockS3TransferHandler.mockResolvedValue(
mockBinaryResponse(createMultipartUploadSuccessResponse as any),
);
const integrityError = new IntegrityError();
mockIsValidObjectUrl.mockImplementationOnce(() => {
throw integrityError;
});
expect(
createMultipartUpload(defaultConfig, {
Bucket: 'bucket',
Key: 'key',
}),
).rejects.toThrow(integrityError);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
bothNilOrEqual,
isEqual,
isNil,
isObject,
} from '../../../../../../src/providers/s3/utils/client/utils/integrityHelpers';

describe('isNil', () => {
it.each([
['undefined', undefined, true],
['null', null, true],
['object', {}, false],
['string', 'string', false],
['empty string', '', false],
['false', false, false],
])('should correctly evaluate %s', (_, input, expected) => {
expect(isNil(input)).toBe(expected);
});
});

describe('bothNilorEqual', () => {
it.each([
['both undefined', undefined, undefined, true],
['both null', null, null, true],
['null and undefined', null, undefined, true],
['both equal', 'mock', 'mock', true],
['undefined and falsy', undefined, '', false],
['truthy and null', 'mock', null, false],
['different strings', 'mock-1', 'mock-2', false],
])(
'should correctly compare %s',
(_, original: any, output: any, expected) => {
expect(bothNilOrEqual(original, output)).toBe(expected);
},
);
});

describe('Integrity Helpers Tests', () => {
describe('isObjectLike', () => {
// Generate all test cases for isObjectLike function here
test.each([
[{}, true],
[{ a: 1 }, true],
[[1, 2, 3], false],
[null, false],
[undefined, false],
['', false],
[1, false],
])('isObjectLike(%p) = %p', (value, expected) => {
expect(isObject(value)).toBe(expected);
});
});

describe('isEqual', () => {
test.each([
[1, 1, true],
[1, 2, false],
[1, '1', false],
['1', '1', true],
['1', '2', false],
[{ a: 1 }, { a: 1 }, true],
[{ a: 1 }, { a: 2 }, false],
[{ a: 1 }, { b: 1 }, false],
[[1, 2], [1, 2], true],
[[1, 2], [2, 1], false],
[[1, 2], [1, 2, 3], false],
])('isEqual(%p, %p) = %p', (a, b, expected) => {
expect(isEqual(a, b)).toBe(expected);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
getRetryDecider as getDefaultRetryDecider,
} from '@aws-amplify/core/internals/aws-client-utils';

import { retryDecider } from '../../../../../../../src/providers/s3/utils/client/utils';
import { parseXmlError } from '../../../../../../../src/providers/s3/utils/client/utils/parsePayload';
import { retryDecider } from '../../../../../../src/providers/s3/utils/client/utils';
import { parseXmlError } from '../../../../../../src/providers/s3/utils/client/utils/parsePayload';

jest.mock(
'../../../../../../../src/providers/s3/utils/client/utils/parsePayload',
Expand Down
Loading

0 comments on commit c018013

Please sign in to comment.