-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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(storage): add support for conditional headers to copy, and validate serialization #13772
Merged
ashika112
merged 9 commits into
aws-amplify:storage-browser/integrity
from
wuuxigh:copy-headers
Sep 12, 2024
+373
−4
Merged
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4b2af3d
feat: add notModifiedSince and eTag to copy
wuuxigh 6cee5de
Merge branch 'staging' into copy-headers
eppjame 61f627e
missing tests for durability helpers
eppjame 93a3ef7
add tests for integrity helpers
eppjame 046e724
Merge branch 'staging' into copy-headers
eppjame 0f7c3e9
feat: add URL validation and tests for copyObject
eppjame 7173e70
chore: increase bundle size of storage:copy
eppjame c38d90b
feat: clean-up copy header validation logic
eppjame 48d84bb
fix: revert copy option interface name changes
eppjame File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
34 changes: 34 additions & 0 deletions
34
packages/storage/__tests__/providers/s3/utils/client/S3/utils/integrityHelpers.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,34 @@ | ||
import { | ||
bothNilOrEqual, | ||
isNil, | ||
} 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); | ||
}, | ||
); | ||
}); |
191 changes: 191 additions & 0 deletions
191
packages/storage/__tests__/providers/s3/utils/client/s3Data/copyObject.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,191 @@ | ||
import { HttpResponse } from '@aws-amplify/core/internals/aws-client-utils'; | ||
|
||
import { s3TransferHandler } from '../../../../../../src/providers/s3/utils/client/runtime/s3TransferHandler/fetch'; | ||
import { | ||
copyObject, | ||
validateCopyObjectHeaders, | ||
} from '../../../../../../src/providers/s3/utils/client/s3data/copyObject'; | ||
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 copyObjectSuccessResponse: any = { | ||
status: 200, | ||
headers: DEFAULT_RESPONSE_HEADERS, | ||
body: '', | ||
}; | ||
|
||
describe('copyObjectSerializer', () => { | ||
const mockIsValidObjectUrl = jest.mocked(validateObjectUrl); | ||
beforeEach(() => { | ||
mockS3TransferHandler.mockReset(); | ||
}); | ||
|
||
it('should pass when objectUrl is valid', async () => { | ||
expect.assertions(1); | ||
mockS3TransferHandler.mockResolvedValue( | ||
mockBinaryResponse(copyObjectSuccessResponse), | ||
); | ||
const output = await copyObject(defaultConfig, { | ||
CopySource: 'mock-source', | ||
Bucket: 'bucket', | ||
Key: 'key', | ||
}); | ||
expect(output).toEqual({ | ||
$metadata: expect.objectContaining(expectedMetadata), | ||
}); | ||
}); | ||
|
||
it('should fail when objectUrl is NOT valid', async () => { | ||
expect.assertions(1); | ||
mockS3TransferHandler.mockResolvedValue( | ||
mockBinaryResponse(copyObjectSuccessResponse), | ||
); | ||
const integrityError = new IntegrityError(); | ||
mockIsValidObjectUrl.mockImplementationOnce(() => { | ||
throw integrityError; | ||
}); | ||
expect( | ||
copyObject(defaultConfig, { | ||
CopySource: 'mock-source', | ||
Bucket: 'bucket', | ||
Key: 'key', | ||
}), | ||
).rejects.toThrow(integrityError); | ||
}); | ||
}); | ||
|
||
describe('validateCopyObjectHeaders', () => { | ||
const baseRequest: any = { CopySource: 'mock-source' }; | ||
const baseHeaders: any = { 'x-amz-copy-source': 'mock-source' }; | ||
|
||
[ | ||
{ | ||
description: 'when only correct copy source is provided', | ||
request: baseRequest, | ||
headers: baseHeaders, | ||
expectPass: true, | ||
}, | ||
{ | ||
description: 'when optional headers are provided correctly', | ||
request: { | ||
...baseRequest, | ||
MetadataDirective: 'mock-metadata', | ||
CopySourceIfMatch: 'mock-etag', | ||
CopySourceIfUnmodifiedSince: new Date(0), | ||
}, | ||
headers: { | ||
...baseHeaders, | ||
'x-amz-metadata-directive': 'mock-metadata', | ||
'x-amz-copy-source-if-match': 'mock-etag', | ||
'x-amz-copy-source-if-unmodified-since': '1970-01-01T00:00:00.000Z', | ||
}, | ||
expectPass: true, | ||
}, | ||
{ | ||
description: 'when optional headers are added without request', | ||
request: baseRequest, | ||
headers: { | ||
...baseHeaders, | ||
'x-amz-metadata-directive': 'mock-metadata', | ||
'x-amz-copy-source-if-match': 'mock-etag', | ||
'x-amz-copy-source-if-unmodified-since': '1970-01-01T00:00:00.000Z', | ||
}, | ||
expectPass: false, | ||
}, | ||
...[null, undefined, 'wrong-metadata'].map(incorrectHeader => ({ | ||
description: `when metadata is not mapped correctly: ${incorrectHeader}`, | ||
request: { | ||
...baseRequest, | ||
MetadataDirective: 'mock-metadata', | ||
}, | ||
headers: { | ||
...baseHeaders, | ||
'x-amz-metadata-directive': incorrectHeader, | ||
}, | ||
expectPass: false, | ||
})), | ||
...[null, undefined, 'wrong-etag'].map(incorrectHeader => ({ | ||
description: `when source etag is not mapped correctly: ${incorrectHeader}`, | ||
request: { | ||
...baseRequest, | ||
CopySourceIfMatch: 'mock-etag', | ||
}, | ||
headers: { | ||
...baseHeaders, | ||
'x-amz-copy-source-if-match': incorrectHeader, | ||
}, | ||
expectPass: false, | ||
})), | ||
...[null, undefined, 'wrong-date'].map(incorrectHeader => ({ | ||
description: `when unmodified since date is not mapped correctly: ${incorrectHeader}`, | ||
request: { | ||
...baseRequest, | ||
CopySourceIfUnmodifiedSince: new Date(0), | ||
}, | ||
headers: { | ||
...baseHeaders, | ||
'x-amz-copy-source-if-unmodified-since': incorrectHeader, | ||
}, | ||
expectPass: false, | ||
})), | ||
].forEach(({ description, request, headers, expectPass }) => { | ||
describe(description, () => { | ||
if (expectPass) { | ||
it('should pass validation', () => { | ||
try { | ||
validateCopyObjectHeaders(request, headers); | ||
} catch (_) { | ||
fail('test case should succeed'); | ||
} | ||
}); | ||
} else { | ||
it('should fail validation', () => { | ||
expect.assertions(1); | ||
try { | ||
validateCopyObjectHeaders(request, headers); | ||
fail('test case should fail'); | ||
} catch (e: any) { | ||
expect(e.name).toBe('Unknown'); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
}); |
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
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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could use inline doc for explaining the util