Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: DownloadData refactor #13073

Merged
merged 20 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/push-integ-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ concurrency:
on:
push:
branches:
- next/main
- feat/gen2-downloadData

jobs:
e2e:
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@
"name": "[Storage] downloadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ downloadData }",
"limit": "14.00 kB"
"limit": "14.05 kB"
},
{
"name": "[Storage] getProperties (S3)",
Expand Down
138 changes: 132 additions & 6 deletions packages/storage/__tests__/providers/s3/apis/downloadData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { AWSCredentials } from '@aws-amplify/core/internals/utils';
import { Amplify } from '@aws-amplify/core';
import { getObject } from '../../../../src/providers/s3/utils/client';
import { downloadData } from '../../../../src/providers/s3';
import { createDownloadTask } from '../../../../src/providers/s3/utils';
import { createDownloadTask, validateStorageOperationInput } from '../../../../src/providers/s3/utils';
import { DownloadDataOptions } from '../../../../src/providers/s3/types';
import { STORAGE_INPUT_KEY, STORAGE_INPUT_PATH } from '../../../../src/providers/s3/utils/constants';

jest.mock('../../../../src/providers/s3/utils/client');
jest.mock('../../../../src/providers/s3/utils');
Expand Down Expand Up @@ -34,9 +35,10 @@ const defaultIdentityId = 'defaultIdentityId';

const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock;
const mockCreateDownloadTask = createDownloadTask as jest.Mock;
const mockValidateStorageInput = validateStorageOperationInput as jest.Mock;
ashika112 marked this conversation as resolved.
Show resolved Hide resolved
const mockGetConfig = Amplify.getConfig as jest.Mock;

describe('downloadData', () => {
describe('downloadData with key', () => {
beforeAll(() => {
mockFetchAuthSession.mockResolvedValue({
credentials,
Expand All @@ -52,12 +54,13 @@ describe('downloadData', () => {
});
});
mockCreateDownloadTask.mockReturnValue('downloadTask');
mockValidateStorageInput.mockReturnValue({inputType: STORAGE_INPUT_KEY, objectKey: key})

beforeEach(() => {
jest.clearAllMocks();
});

it('should return a download task', async () => {
it('should return a download task with key', async () => {
expect(downloadData({ key: 'key' })).toBe('downloadTask');
});

Expand Down Expand Up @@ -88,7 +91,6 @@ describe('downloadData', () => {
: '';

it(`should supply the correct parameters to getObject API handler with ${accessLevelMsg} accessLevel ${targetIdentityIdMsg}`, async () => {
expect.assertions(2);
(getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' });
const onProgress = jest.fn();
downloadData({
Expand Down Expand Up @@ -119,8 +121,7 @@ describe('downloadData', () => {
});
});

it('should assign the getObject API handler response to the result', async () => {
expect.assertions(2);
it('should assign the getObject API handler response to the result with key', async () => {
const lastModified = 'lastModified';
const contentLength = 'contentLength';
const eTag = 'eTag';
Expand Down Expand Up @@ -177,3 +178,128 @@ describe('downloadData', () => {
);
});
});

describe('downloadData with path', () => {
beforeAll(() => {
mockFetchAuthSession.mockResolvedValue({
credentials,
identityId: defaultIdentityId,
});
mockGetConfig.mockReturnValue({
Storage: {
S3: {
bucket,
region,
},
},
});
mockCreateDownloadTask.mockReturnValue('downloadTask');
mockValidateStorageInput.mockReturnValue({inputType: STORAGE_INPUT_PATH, objectKey: 'path'})
});

beforeEach(() => {
jest.clearAllMocks();
});

it('should return a download task with path', async () => {
expect(downloadData({ path: 'path' })).toBe('downloadTask');
});

[
{
path: 'path',
expectedKey: 'path'
},
{
path: () => 'path',
expectedKey: 'path'
}
].forEach(({ path, expectedKey }) => {
ashika112 marked this conversation as resolved.
Show resolved Hide resolved
it('should supply the correct parameters to getObject API handler', async () => {
(getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' });
const onProgress = jest.fn();
downloadData({
path: path,
options: {
useAccelerateEndpoint: true,
onProgress,
} as DownloadDataOptions,
});
const job = mockCreateDownloadTask.mock.calls[0][0].job;
await job();
expect(getObject).toHaveBeenCalledTimes(1);
expect(getObject).toHaveBeenCalledWith(
{
credentials,
region,
useAccelerateEndpoint: true,
onDownloadProgress: onProgress,
abortSignal: expect.any(AbortSignal),
userAgentValue: expect.any(String),
},
{
Bucket: bucket,
Key: expectedKey,
},
);
});
});

it('should assign the getObject API handler response to the result with path', async () => {
const lastModified = 'lastModified';
const contentLength = 'contentLength';
const eTag = 'eTag';
const metadata = 'metadata';
const versionId = 'versionId';
const contentType = 'contentType';
const body = 'body';
const path = 'path';
(getObject as jest.Mock).mockResolvedValueOnce({
Body: body,
LastModified: lastModified,
ContentLength: contentLength,
ETag: eTag,
Metadata: metadata,
VersionId: versionId,
ContentType: contentType,
});
downloadData({ path });
const job = mockCreateDownloadTask.mock.calls[0][0].job;
const result = await job();
expect(getObject).toHaveBeenCalledTimes(1);
expect(result).toEqual({
path,
body,
lastModified,
size: contentLength,
eTag,
metadata,
versionId,
contentType,
});
});


it('should forward the bytes range option to the getObject API', async () => {
const start = 1;
const end = 100;
(getObject as jest.Mock).mockResolvedValueOnce({ Body: 'body' });

downloadData({
path: 'mockPath',
options: {
bytesRange: { start, end },
},
});

const job = mockCreateDownloadTask.mock.calls[0][0].job;
await job();

expect(getObject).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
Range: `bytes=${start}-${end}`,
}),
);
});
});
ashika112 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { validateStorageOperationInput } from "../../../../../src/providers/s3/utils";
import { STORAGE_INPUT_KEY, STORAGE_INPUT_PATH } from "../../../../../src/providers/s3/utils/constants";

describe('validateStorageOperationInput', () => {
it('should return inputType as STORAGE_INPUT_PATH and objectKey as testPath when input is path as string', () => {
const input = { path: 'testPath' };
const result = validateStorageOperationInput(input);
expect(result).toEqual({ inputType: STORAGE_INPUT_PATH, objectKey: 'testPath' });
});

it('should return inputType as STORAGE_INPUT_PATH and objectKey as result of path function when input is path as function', () => {
const input = { path: ({identityId}: {identityId?: string}) => `testPath/${identityId}` };
const result = validateStorageOperationInput(input, '123');
expect(result).toEqual({ inputType: STORAGE_INPUT_PATH, objectKey: 'testPath/123' });
});

it('should return inputType as STORAGE_INPUT_KEY and objectKey as testKey when input is key', () => {
const input = { key: 'testKey' };
const result = validateStorageOperationInput(input);
expect(result).toEqual({ inputType: STORAGE_INPUT_KEY, objectKey: 'testKey' });
});

it('should throw an error when input is invalid', () => {
const input = {invalid: 'test' } as any;
expect(() => validateStorageOperationInput(input)).toThrow('invalid input');
});
});
ashika112 marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading