Skip to content

Commit 6d5173c

Browse files
committed
test(storage): add unit tests for PUT method functionality
1 parent 51e3116 commit 6d5173c

File tree

2 files changed

+296
-0
lines changed

2 files changed

+296
-0
lines changed

packages/storage/__tests__/providers/s3/apis/internal/getUrl.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Amplify, StorageAccessLevel } from '@aws-amplify/core';
77
import { getUrl } from '../../../../../src/providers/s3/apis/internal/getUrl';
88
import {
99
getPresignedGetObjectUrl,
10+
getPresignedPutObjectUrl,
1011
headObject,
1112
} from '../../../../../src/providers/s3/utils/client/s3data';
1213
import {
@@ -79,6 +80,7 @@ describe('getUrl test with key', () => {
7980
$metadata: {} as any,
8081
});
8182
jest.mocked(getPresignedGetObjectUrl).mockResolvedValue(mockURL);
83+
jest.mocked(getPresignedPutObjectUrl).mockResolvedValue(mockURL);
8284
});
8385
afterEach(() => {
8486
jest.clearAllMocks();
@@ -225,6 +227,87 @@ describe('getUrl test with key', () => {
225227
);
226228
});
227229
});
230+
231+
describe('method PUT for presigned upload URLs', () => {
232+
it('should generate PUT presigned URL and skip validation', async () => {
233+
await getUrlWrapper({
234+
key: 'key',
235+
options: {
236+
method: 'PUT',
237+
validateObjectExistence: true,
238+
},
239+
});
240+
expect(getPresignedPutObjectUrl).toHaveBeenCalledTimes(1);
241+
expect(headObject).not.toHaveBeenCalled();
242+
await expect(getPresignedPutObjectUrl).toBeLastCalledWithConfigAndInput(
243+
{
244+
credentials,
245+
region,
246+
expiration: expect.any(Number),
247+
},
248+
{
249+
Bucket: bucket,
250+
Key: 'public/key',
251+
},
252+
);
253+
});
254+
255+
it('should include content type and disposition for PUT', async () => {
256+
const contentType = 'image/jpeg';
257+
const contentDisposition = 'attachment; filename="test.jpg"';
258+
const cacheControl = 'max-age=3600';
259+
await getUrlWrapper({
260+
key: 'key',
261+
options: {
262+
method: 'PUT',
263+
contentType,
264+
contentDisposition,
265+
cacheControl,
266+
},
267+
});
268+
expect(getPresignedPutObjectUrl).toHaveBeenCalledTimes(1);
269+
await expect(getPresignedPutObjectUrl).toBeLastCalledWithConfigAndInput(
270+
{
271+
credentials,
272+
region,
273+
expiration: expect.any(Number),
274+
},
275+
{
276+
Bucket: bucket,
277+
Key: 'public/key',
278+
ContentType: contentType,
279+
ContentDisposition: contentDisposition,
280+
CacheControl: cacheControl,
281+
},
282+
);
283+
});
284+
285+
it('should handle object content disposition for PUT', async () => {
286+
await getUrlWrapper({
287+
key: 'key',
288+
options: {
289+
method: 'PUT',
290+
contentDisposition: {
291+
type: 'attachment',
292+
filename: 'test.pdf',
293+
},
294+
},
295+
});
296+
expect(getPresignedPutObjectUrl).toHaveBeenCalledTimes(1);
297+
await expect(getPresignedPutObjectUrl).toBeLastCalledWithConfigAndInput(
298+
{
299+
credentials,
300+
region,
301+
expiration: expect.any(Number),
302+
},
303+
{
304+
Bucket: bucket,
305+
Key: 'public/key',
306+
ContentDisposition: 'attachment; filename="test.pdf"',
307+
},
308+
);
309+
});
310+
});
228311
});
229312
describe('Error cases : With key', () => {
230313
afterAll(() => {
@@ -285,6 +368,7 @@ describe('getUrl test with path', () => {
285368
$metadata: {} as any,
286369
});
287370
jest.mocked(getPresignedGetObjectUrl).mockResolvedValue(mockURL);
371+
jest.mocked(getPresignedPutObjectUrl).mockResolvedValue(mockURL);
288372
});
289373
afterEach(() => {
290374
jest.clearAllMocks();
@@ -418,6 +502,56 @@ describe('getUrl test with path', () => {
418502
);
419503
});
420504
});
505+
506+
describe('method PUT for presigned upload URLs with path', () => {
507+
it('should generate PUT presigned URL with path and skip validation', async () => {
508+
const inputPath = 'uploads/file.jpg';
509+
await getUrlWrapper({
510+
path: inputPath,
511+
options: {
512+
method: 'PUT',
513+
validateObjectExistence: true,
514+
},
515+
});
516+
expect(getPresignedPutObjectUrl).toHaveBeenCalledTimes(1);
517+
expect(headObject).not.toHaveBeenCalled();
518+
await expect(getPresignedPutObjectUrl).toBeLastCalledWithConfigAndInput(
519+
{
520+
credentials,
521+
region,
522+
expiration: expect.any(Number),
523+
},
524+
{
525+
Bucket: bucket,
526+
Key: inputPath,
527+
},
528+
);
529+
});
530+
531+
it('should include expectedBucketOwner for PUT with path', async () => {
532+
const inputPath = 'uploads/file.jpg';
533+
await getUrlWrapper({
534+
path: inputPath,
535+
options: {
536+
method: 'PUT',
537+
expectedBucketOwner: validBucketOwner,
538+
},
539+
});
540+
expect(getPresignedPutObjectUrl).toHaveBeenCalledTimes(1);
541+
await expect(getPresignedPutObjectUrl).toBeLastCalledWithConfigAndInput(
542+
{
543+
credentials,
544+
region,
545+
expiration: expect.any(Number),
546+
},
547+
{
548+
Bucket: bucket,
549+
Key: inputPath,
550+
ExpectedBucketOwner: validBucketOwner,
551+
},
552+
);
553+
});
554+
});
421555
});
422556
describe('Happy cases: With path and Content Disposition, Content Type', () => {
423557
const config = {
@@ -435,6 +569,7 @@ describe('getUrl test with path', () => {
435569
$metadata: {} as any,
436570
});
437571
jest.mocked(getPresignedGetObjectUrl).mockResolvedValue(mockURL);
572+
jest.mocked(getPresignedPutObjectUrl).mockResolvedValue(mockURL);
438573
});
439574
afterEach(() => {
440575
jest.clearAllMocks();
@@ -500,6 +635,7 @@ describe('getUrl test with path', () => {
500635
$metadata: {} as any,
501636
});
502637
jest.mocked(getPresignedGetObjectUrl).mockResolvedValue(mockURL);
638+
jest.mocked(getPresignedPutObjectUrl).mockResolvedValue(mockURL);
503639
});
504640

505641
afterEach(() => {
@@ -660,4 +796,30 @@ describe(`getURL with path and Expected Bucket Owner`, () => {
660796

661797
expect(getPresignedGetObjectUrl).not.toHaveBeenCalled();
662798
});
799+
800+
it('should pass expectedBucketOwner to getPresignedPutObjectUrl for PUT method', async () => {
801+
const path = 'public/expectedbucketowner_test';
802+
803+
await getUrlWrapper({
804+
path,
805+
options: {
806+
method: 'PUT',
807+
expectedBucketOwner: validBucketOwner,
808+
},
809+
});
810+
811+
expect(getPresignedPutObjectUrl).toHaveBeenCalledTimes(1);
812+
await expect(getPresignedPutObjectUrl).toBeLastCalledWithConfigAndInput(
813+
{
814+
credentials,
815+
region,
816+
expiration: expect.any(Number),
817+
},
818+
{
819+
Bucket: bucket,
820+
ExpectedBucketOwner: validBucketOwner,
821+
Key: path,
822+
},
823+
);
824+
});
663825
});
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import {
5+
UNSIGNED_PAYLOAD,
6+
presignUrl,
7+
} from '@aws-amplify/core/internals/aws-client-utils';
8+
9+
import { getPresignedPutObjectUrl } from '../../../../../../src/providers/s3/utils/client/s3data';
10+
11+
import { defaultConfigWithStaticCredentials } from './cases/shared';
12+
13+
jest.mock('@aws-amplify/core/internals/aws-client-utils', () => {
14+
const original = jest.requireActual(
15+
'@aws-amplify/core/internals/aws-client-utils',
16+
);
17+
const { presignUrl: getPresignedUrl } = original;
18+
19+
return {
20+
...original,
21+
presignUrl: jest.fn((...args) => getPresignedUrl(...args)),
22+
};
23+
});
24+
25+
const mockPresignUrl = presignUrl as jest.Mock;
26+
27+
describe('getPresignedPutObjectUrl', () => {
28+
it('should return put object API request', async () => {
29+
const actual = await getPresignedPutObjectUrl(
30+
{
31+
...defaultConfigWithStaticCredentials,
32+
signingRegion: defaultConfigWithStaticCredentials.region,
33+
signingService: 's3',
34+
expiration: 900,
35+
userAgentValue: 'UA',
36+
},
37+
{
38+
Bucket: 'bucket',
39+
Key: 'key',
40+
},
41+
);
42+
const actualUrl = actual;
43+
expect(actualUrl.hostname).toEqual(
44+
`bucket.s3.${defaultConfigWithStaticCredentials.region}.amazonaws.com`,
45+
);
46+
expect(actualUrl.pathname).toEqual('/key');
47+
expect(actualUrl.searchParams.get('X-Amz-Expires')).toEqual('900');
48+
expect(actualUrl.searchParams.get('x-amz-user-agent')).toEqual('UA');
49+
});
50+
51+
it('should call presignUrl with uriEscapePath param set to false', async () => {
52+
await getPresignedPutObjectUrl(
53+
{
54+
...defaultConfigWithStaticCredentials,
55+
signingRegion: defaultConfigWithStaticCredentials.region,
56+
signingService: 's3',
57+
expiration: 900,
58+
userAgentValue: 'UA',
59+
},
60+
{
61+
Bucket: 'bucket',
62+
Key: 'key',
63+
},
64+
);
65+
66+
expect(mockPresignUrl).toHaveBeenCalledWith(
67+
expect.anything(),
68+
expect.objectContaining({
69+
uriEscapePath: false,
70+
}),
71+
);
72+
});
73+
74+
it('should return put object API request with content type and disposition', async () => {
75+
const actual = await getPresignedPutObjectUrl(
76+
{
77+
...defaultConfigWithStaticCredentials,
78+
signingRegion: defaultConfigWithStaticCredentials.region,
79+
signingService: 's3',
80+
expiration: 900,
81+
userAgentValue: 'UA',
82+
},
83+
{
84+
Bucket: 'bucket',
85+
Key: 'key',
86+
ContentType: 'image/jpeg',
87+
ContentDisposition: 'attachment; filename="photo.jpg"',
88+
},
89+
);
90+
91+
expect(actual).toEqual(
92+
expect.objectContaining({
93+
hostname: `bucket.s3.${defaultConfigWithStaticCredentials.region}.amazonaws.com`,
94+
pathname: '/key',
95+
searchParams: expect.objectContaining({
96+
get: expect.any(Function),
97+
}),
98+
}),
99+
);
100+
101+
expect(actual.searchParams.get('X-Amz-Expires')).toBe('900');
102+
expect(actual.searchParams.get('content-type')).toBe('image/jpeg');
103+
expect(actual.searchParams.get('content-disposition')).toBe(
104+
'attachment; filename="photo.jpg"',
105+
);
106+
expect(actual.searchParams.get('x-amz-user-agent')).toBe('UA');
107+
});
108+
109+
it('should use UNSIGNED-PAYLOAD for presigned URLs', async () => {
110+
mockPresignUrl.mockClear();
111+
112+
const result = await getPresignedPutObjectUrl(
113+
{
114+
...defaultConfigWithStaticCredentials,
115+
signingRegion: defaultConfigWithStaticCredentials.region,
116+
signingService: 's3',
117+
expiration: 900,
118+
},
119+
{
120+
Bucket: 'bucket',
121+
Key: 'key',
122+
},
123+
);
124+
125+
expect(mockPresignUrl).toHaveBeenCalledWith(
126+
expect.objectContaining({
127+
body: UNSIGNED_PAYLOAD,
128+
}),
129+
expect.anything(),
130+
);
131+
132+
expect(result.searchParams.get('x-amz-content-sha256')).toBeNull();
133+
});
134+
});

0 commit comments

Comments
 (0)