-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(storage): refactor unit tests for public & internal facade (#13955)
- Loading branch information
1 parent
ff96618
commit 4d65a05
Showing
23 changed files
with
4,086 additions
and
3,479 deletions.
There are no files selected for viewing
551 changes: 30 additions & 521 deletions
551
packages/storage/__tests__/providers/s3/apis/copy.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
553 changes: 23 additions & 530 deletions
553
packages/storage/__tests__/providers/s3/apis/downloadData.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
508 changes: 23 additions & 485 deletions
508
packages/storage/__tests__/providers/s3/apis/getProperties.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
588 changes: 22 additions & 566 deletions
588
packages/storage/__tests__/providers/s3/apis/getUrl.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
533 changes: 533 additions & 0 deletions
533
packages/storage/__tests__/providers/s3/apis/internal/copy.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
547 changes: 547 additions & 0 deletions
547
packages/storage/__tests__/providers/s3/apis/internal/downloadData.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
500 changes: 500 additions & 0 deletions
500
packages/storage/__tests__/providers/s3/apis/internal/getProperties.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
575 changes: 575 additions & 0 deletions
575
packages/storage/__tests__/providers/s3/apis/internal/getUrl.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
1,027 changes: 1,027 additions & 0 deletions
1,027
packages/storage/__tests__/providers/s3/apis/internal/list.test.ts
Large diffs are not rendered by default.
Oops, something went wrong.
337 changes: 337 additions & 0 deletions
337
packages/storage/__tests__/providers/s3/apis/internal/remove.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
); | ||
} | ||
}); | ||
}); | ||
}); |
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
...ers/s3/apis/uploadData/byteLength.test.ts → ...is/internal/uploadData/byteLength.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.