Skip to content

Commit

Permalink
added content disposition and content type for getUrl and corresponde…
Browse files Browse the repository at this point in the history
…d unit tests
  • Loading branch information
yuhengshs committed Jul 18, 2024
1 parent 60885b7 commit 739a1d4
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 1 deletion.
62 changes: 62 additions & 0 deletions packages/storage/__tests__/providers/s3/apis/getUrl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,68 @@ describe('getUrl test with path', () => {
},
);
});
describe('Happy cases: With path and Content Disposition, Content Type', () => {
const config = {
credentials,
region,
userAgentValue: expect.any(String),
};
beforeEach(() => {
jest.mocked(headObject).mockResolvedValue({
ContentLength: 100,
ContentType: 'text/plain',
ETag: 'etag',
LastModified: new Date('01-01-1980'),
Metadata: { meta: 'value' },
$metadata: {} as any,
});
jest.mocked(getPresignedGetObjectUrl).mockResolvedValue(mockURL);
});
afterEach(() => {
jest.clearAllMocks();
});

test.each([
{
path: 'path',
expectedKey: 'path',
contentDisposition: 'inline; filename="example.txt"',
contentType: 'text/plain',
},
{
path: () => 'path',
expectedKey: 'path',
contentDisposition: 'attachment; filename="download.pdf"',
contentType: 'application/pdf',
},
])(
'should getUrl with path $path and expectedKey $expectedKey and content disposition and content type',
async ({ path, expectedKey, contentDisposition, contentType }) => {
const headObjectOptions = {
Bucket: bucket,
Key: expectedKey,
};
const { url, expiresAt } = await getUrlWrapper({
path,
options: {
validateObjectExistence: true,
contentDisposition,
contentType,
},
});
expect(getPresignedGetObjectUrl).toHaveBeenCalledTimes(1);
expect(headObject).toHaveBeenCalledTimes(1);
await expect(headObject).toBeLastCalledWithConfigAndInput(
config,
headObjectOptions,
);
expect({ url, expiresAt }).toEqual({
url: mockURL,
expiresAt: expect.any(Date),
});
},
);
});
describe('Error cases : With path', () => {
afterAll(() => {
jest.clearAllMocks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,38 @@ describe('serializeGetObjectRequest', () => {
}),
);
});

it('should return get object API request with disposition and content type', async () => {
const actual = await getPresignedGetObjectUrl(
{
...defaultConfigWithStaticCredentials,
signingRegion: defaultConfigWithStaticCredentials.region,
signingService: 's3',
expiration: 900,
userAgentValue: 'UA',
},
{
Bucket: 'bucket',
Key: 'key',
ResponseContentDisposition: 'attachment; filename="filename.jpg"',
ResponseContentType: 'application/pdf',
},
);
const actualUrl = actual;
expect(actualUrl.hostname).toEqual(
`bucket.s3.${defaultConfigWithStaticCredentials.region}.amazonaws.com`,
);
expect(actualUrl.pathname).toEqual('/key');
expect(actualUrl.searchParams.get('X-Amz-Expires')).toEqual('900');
expect(actualUrl.searchParams.get('x-amz-content-sha256')).toEqual(
expect.any(String),
);
expect(actualUrl.searchParams.get('response-content-disposition')).toEqual(
'attachment; filename="filename.jpg"',
);
expect(actualUrl.searchParams.get('response-content-type')).toEqual(
'application/pdf',
);
expect(actualUrl.searchParams.get('x-amz-user-agent')).toEqual('UA');
});
});
6 changes: 6 additions & 0 deletions packages/storage/src/providers/s3/apis/internal/getUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export const getUrl = async (
{
Bucket: bucket,
Key: finalKey,
...(getUrlOptions?.contentDisposition && {
ResponseContentDisposition: getUrlOptions.contentDisposition,
}),
...(getUrlOptions?.contentType && {
ResponseContentEncoding: getUrlOptions.contentType,
}),
},
),
expiresAt: new Date(Date.now() + urlExpirationInSec * 1000),
Expand Down
10 changes: 10 additions & 0 deletions packages/storage/src/providers/s3/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ export type GetUrlOptions = CommonOptions & {
* @default 900 (15 minutes)
*/
expiresIn?: number;
/**
* The content-disposition header value of the file when downloading it.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
*/
contentDisposition?: string;
/**
* The content-type header value of the file when downloading it.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
*/
contentType?: string;
};

/** @deprecated Use {@link GetUrlOptionsWithPath} instead. */
Expand Down
15 changes: 14 additions & 1 deletion packages/storage/src/providers/s3/utils/client/getObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ const USER_AGENT_HEADER = 'x-amz-user-agent';

export type GetObjectInput = Pick<
GetObjectCommandInput,
'Bucket' | 'Key' | 'Range'
| 'Bucket'
| 'Key'
| 'Range'
| 'ResponseContentDisposition'
| 'ResponseContentType'
>;

export type GetObjectOutput = GetObjectCommandOutput;
Expand Down Expand Up @@ -156,6 +160,15 @@ export const getPresignedGetObjectUrl = async (
config.userAgentValue,
);
}
if (input.ResponseContentType) {
url.searchParams.append('response-content-type', input.ResponseContentType);
}
if (input.ResponseContentDisposition) {
url.searchParams.append(
'response-content-disposition',
input.ResponseContentDisposition,
);
}

for (const [headerName, value] of Object.entries(headers).sort(
([key1], [key2]) => key1.localeCompare(key2),
Expand Down

0 comments on commit 739a1d4

Please sign in to comment.