Skip to content

Commit

Permalink
test(storage): refactor unit tests for public & internal facade (#13955)
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanZhengYP authored Oct 29, 2024
1 parent ff96618 commit 4d65a05
Show file tree
Hide file tree
Showing 23 changed files with 4,086 additions and 3,479 deletions.
551 changes: 30 additions & 521 deletions packages/storage/__tests__/providers/s3/apis/copy.test.ts

Large diffs are not rendered by default.

553 changes: 23 additions & 530 deletions packages/storage/__tests__/providers/s3/apis/downloadData.test.ts

Large diffs are not rendered by default.

508 changes: 23 additions & 485 deletions packages/storage/__tests__/providers/s3/apis/getProperties.test.ts

Large diffs are not rendered by default.

588 changes: 22 additions & 566 deletions packages/storage/__tests__/providers/s3/apis/getUrl.test.ts

Large diffs are not rendered by default.

533 changes: 533 additions & 0 deletions packages/storage/__tests__/providers/s3/apis/internal/copy.test.ts

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

575 changes: 575 additions & 0 deletions packages/storage/__tests__/providers/s3/apis/internal/getUrl.test.ts

Large diffs are not rendered by default.

1,027 changes: 1,027 additions & 0 deletions packages/storage/__tests__/providers/s3/apis/internal/list.test.ts

Large diffs are not rendered by default.

337 changes: 337 additions & 0 deletions packages/storage/__tests__/providers/s3/apis/internal/remove.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { AWSCredentials } from '@aws-amplify/core/internals/utils';
import { Amplify, StorageAccessLevel } from '@aws-amplify/core';

import { deleteObject } from '../../../../../src/providers/s3/utils/client/s3data';
import { remove } from '../../../../../src/providers/s3/apis/internal/remove';
import { StorageValidationErrorCode } from '../../../../../src/errors/types/validation';
import {
RemoveInput,
RemoveOutput,
RemoveWithPathInput,
RemoveWithPathOutput,
} from '../../../../../src/providers/s3/types';
import './testUtils';

jest.mock('../../../../../src/providers/s3/utils/client/s3data');
jest.mock('@aws-amplify/core', () => ({
ConsoleLogger: jest.fn().mockImplementation(function ConsoleLogger() {
return { debug: jest.fn() };
}),
Amplify: {
getConfig: jest.fn(),
Auth: {
fetchAuthSession: jest.fn(),
},
},
}));
const mockDeleteObject = deleteObject as jest.Mock;
const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock;
const mockGetConfig = jest.mocked(Amplify.getConfig);
const inputKey = 'key';
const bucket = 'bucket';
const region = 'region';
const defaultIdentityId = 'defaultIdentityId';
const validBucketOwner = '111122223333';
const credentials: AWSCredentials = {
accessKeyId: 'accessKeyId',
sessionToken: 'sessionToken',
secretAccessKey: 'secretAccessKey',
};
const deleteObjectClientConfig = {
credentials,
region,
userAgentValue: expect.any(String),
};

describe('remove API', () => {
beforeAll(() => {
mockFetchAuthSession.mockResolvedValue({
credentials,
identityId: defaultIdentityId,
});
mockGetConfig.mockReturnValue({
Storage: {
S3: {
bucket,
region,
buckets: { 'default-bucket': { bucketName: bucket, region } },
},
},
});
});
describe('Happy Cases', () => {
describe('With Key', () => {
const removeWrapper = (input: RemoveInput) => remove(Amplify, input);

beforeEach(() => {
mockDeleteObject.mockImplementation(() => {
return {
Metadata: { key: 'value' },
};
});
});
afterEach(() => {
jest.clearAllMocks();
});
const testCases: {
expectedKey: string;
options?: { accessLevel?: StorageAccessLevel };
}[] = [
{
expectedKey: `public/${inputKey}`,
},
{
options: { accessLevel: 'guest' },
expectedKey: `public/${inputKey}`,
},
{
options: { accessLevel: 'private' },
expectedKey: `private/${defaultIdentityId}/${inputKey}`,
},
{
options: { accessLevel: 'protected' },
expectedKey: `protected/${defaultIdentityId}/${inputKey}`,
},
];

testCases.forEach(({ options, expectedKey }) => {
const accessLevel = options?.accessLevel ?? 'default';

it(`should remove object with ${accessLevel} accessLevel`, async () => {
const { key } = (await removeWrapper({
key: inputKey,
options,
})) as RemoveOutput;
expect(key).toEqual(inputKey);
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
deleteObjectClientConfig,
{
Bucket: bucket,
Key: expectedKey,
},
);
});
});

describe('bucket passed in options', () => {
it('should override bucket in deleteObject call when bucket is object', async () => {
const mockBucketName = 'bucket-1';
const mockRegion = 'region-1';
await removeWrapper({
key: inputKey,
options: {
bucket: { bucketName: mockBucketName, region: mockRegion },
},
});
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
{
credentials,
region: mockRegion,
userAgentValue: expect.any(String),
},
{
Bucket: mockBucketName,
Key: `public/${inputKey}`,
},
);
});
it('should override bucket in deleteObject call when bucket is string', async () => {
await removeWrapper({
key: inputKey,
options: {
bucket: 'default-bucket',
},
});
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
{
credentials,
region,
userAgentValue: expect.any(String),
},
{
Bucket: bucket,
Key: `public/${inputKey}`,
},
);
});
});
describe('ExpectedBucketOwner passed in options', () => {
it('should include expectedBucketOwner in headers when provided', async () => {
const mockKey = 'test-path';
const mockBucket = 'bucket-1';
const mockRegion = 'region-1';
await removeWrapper({
key: mockKey,
options: {
bucket: { bucketName: mockBucket, region: mockRegion },
expectedBucketOwner: validBucketOwner,
},
});
expect(deleteObject).toHaveBeenCalledTimes(1);
expect(deleteObject).toHaveBeenNthCalledWithConfigAndInput(
1,
expect.any(Object),
expect.objectContaining({
ExpectedBucketOwner: validBucketOwner,
}),
);
});
});
});
describe('With Path', () => {
const removeWrapper = (input: RemoveWithPathInput) =>
remove(Amplify, input);
beforeEach(() => {
mockDeleteObject.mockImplementation(() => {
return {
Metadata: { key: 'value' },
};
});
});
afterEach(() => {
jest.clearAllMocks();
});
[
{
path: `public/${inputKey}`,
},
{
path: ({ identityId }: { identityId?: string }) =>
`protected/${identityId}/${inputKey}`,
},
].forEach(({ path: inputPath }) => {
const resolvedPath =
typeof inputPath === 'string'
? inputPath
: inputPath({ identityId: defaultIdentityId });

it(`should remove object for the given path`, async () => {
const { path } = (await removeWrapper({
path: inputPath,
})) as RemoveWithPathOutput;
expect(path).toEqual(resolvedPath);
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
deleteObjectClientConfig,
{
Bucket: bucket,
Key: resolvedPath,
},
);
});
});

describe('bucket passed in options', () => {
it('should override bucket in deleteObject call when bucket is object', async () => {
const mockBucketName = 'bucket-1';
const mockRegion = 'region-1';
await removeWrapper({
path: 'path/',
options: {
bucket: { bucketName: mockBucketName, region: mockRegion },
},
});
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
{
credentials,
region: mockRegion,
userAgentValue: expect.any(String),
},
{
Bucket: mockBucketName,
Key: 'path/',
},
);
});
it('should override bucket in deleteObject call when bucket is string', async () => {
await removeWrapper({
path: 'path/',
options: {
bucket: 'default-bucket',
},
});
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
{
credentials,
region,
userAgentValue: expect.any(String),
},
{
Bucket: bucket,
Key: 'path/',
},
);
});
});
describe('ExpectedBucketOwner passed in options', () => {
it('should include expectedBucketOwner in headers when provided', async () => {
const mockPath = 'public/test-path';
const mockBucket = 'bucket-1';
const mockRegion = 'region-1';
await removeWrapper({
path: mockPath,
options: {
bucket: { bucketName: mockBucket, region: mockRegion },
expectedBucketOwner: validBucketOwner,
},
});
expect(deleteObject).toHaveBeenCalledTimes(1);
expect(deleteObject).toHaveBeenNthCalledWithConfigAndInput(
1,
expect.any(Object),
expect.objectContaining({
ExpectedBucketOwner: validBucketOwner,
}),
);
});
});
});
});

describe('Error Cases:', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should return a not found error', async () => {
mockDeleteObject.mockRejectedValueOnce(
Object.assign(new Error(), {
$metadata: { httpStatusCode: 404 },
name: 'NotFound',
}),
);
expect.assertions(3);
const key = 'wrongKey';
try {
await remove(Amplify, { key });
} catch (error: any) {
expect(deleteObject).toHaveBeenCalledTimes(1);
await expect(deleteObject).toBeLastCalledWithConfigAndInput(
deleteObjectClientConfig,
{
Bucket: bucket,
Key: `public/${key}`,
},
);
expect(error.$metadata.httpStatusCode).toBe(404);
}
});
it('should throw InvalidStorageOperationInput error when the path is empty', async () => {
expect.assertions(1);
try {
await remove(Amplify, { path: '' });
} catch (error: any) {
expect(error.name).toBe(
StorageValidationErrorCode.InvalidStorageOperationInput,
);
}
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { byteLength } from '../../../../../src/providers/s3/apis/internal/uploadData/byteLength';
import { byteLength } from '../../../../../../src/providers/s3/apis/internal/uploadData/byteLength';

describe('byteLength', () => {
it('returns 0 for null or undefined', () => {
Expand Down
Loading

0 comments on commit 4d65a05

Please sign in to comment.