Skip to content

Commit

Permalink
Merge branch 'next' into next-api-v6
Browse files Browse the repository at this point in the history
  • Loading branch information
david-mcafee committed Sep 5, 2023
2 parents b945cf6 + 7188d04 commit 0787b12
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ describe('createCookieStorageAdapterFromNextServerContext', () => {
it('should throw error when no cookie storage adapter is created from the context', () => {
expect(() =>
createCookieStorageAdapterFromNextServerContext({
request: {} as any,
request: undefined,
response: new ServerResponse({} as any),
} as any)
).toThrowError();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,41 @@ import {
AmplifyServerContextError,
CookieStorage,
} from '@aws-amplify/core/internals/adapter-core';
import { IncomingMessage, ServerResponse } from 'http';

export const DATE_IN_THE_PAST = new Date(0);

export const createCookieStorageAdapterFromNextServerContext = (
context: NextServer.Context
): CookieStorage.Adapter => {
const { request, response } = context as
| NextServer.NextRequestAndNextResponseContext
| NextServer.NextRequestAndResponseContext;
const { request: req, response: res } =
context as Partial<NextServer.GetServerSidePropsContext>;

// When the server context is from `getServerSideProps`, the `req` is an instance
// of IncomingMessage, and the `res` is an instance of ServerResponse.
// We cannot import these two classes here from `http` as it breaks in Next
// Edge Runtime. Hence, we check the methods that we need to use for creating
// cookie adapter.
if (
req &&
res &&
Object.prototype.toString.call(req.cookies) === '[object Object]' &&
typeof res.setHeader === 'function'
) {
return createCookieStorageAdapterFromGetServerSidePropsContext(req, res);
}

if (request instanceof NextRequest && response) {
const { request, response } = context as Partial<
| NextServer.NextRequestAndNextResponseContext
| NextServer.NextRequestAndResponseContext
>;

// When the server context is from `middleware`, the `request` is an instance
// of `NextRequest`.
// When the server context is from a route handler, the `request` is an `Proxy`
// wrapped `Request`.
// The `NextRequest` and the `Proxy` are sharing the same interface by Next
// implementation. So we don't need to detect the difference.
if (request && response) {
if (response instanceof NextResponse) {
return createCookieStorageAdapterFromNextRequestAndNextResponse(
request,
Expand All @@ -32,21 +55,14 @@ export const createCookieStorageAdapterFromNextServerContext = (
}
}

const { cookies } = context as
| NextServer.ServerComponentContext
| NextServer.ServerActionContext;
const { cookies } = context as Partial<
NextServer.ServerComponentContext | NextServer.ServerActionContext
>;

if (typeof cookies === 'function') {
return createCookieStorageAdapterFromNextCookies(cookies);
}

const { request: req, response: res } =
context as NextServer.GetServerSidePropsContext;

if (req instanceof IncomingMessage && res instanceof ServerResponse) {
return createCookieStorageAdapterFromGetServerSidePropsContext(req, res);
}

// This should not happen normally.
throw new AmplifyServerContextError({
message:
Expand Down
16 changes: 8 additions & 8 deletions packages/storage/__tests__/providers/s3/apis/copy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@
// SPDX-License-Identifier: Apache-2.0

import { Credentials } from '@aws-sdk/types';
import {
Amplify,
StorageAccessLevel,
fetchAuthSession,
} from '@aws-amplify/core';
import { Amplify, StorageAccessLevel } from '@aws-amplify/core';
import { copyObject } from '../../../../src/providers/s3/utils/client';
import { copy } from '../../../../src/providers/s3/apis';

jest.mock('../../../../src/providers/s3/utils/client');
jest.mock('@aws-amplify/core', () => ({
fetchAuthSession: jest.fn(),
Amplify: {
getConfig: jest.fn(),
Auth: {
fetchAuthSession: jest.fn(),
},
},
}));
const mockCopyObject = copyObject as jest.Mock;
const mockFetchAuthSession = fetchAuthSession as jest.Mock;
const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock;
const mockGetConfig = Amplify.getConfig as jest.Mock;

const sourceKey = 'sourceKey';
Expand All @@ -43,7 +41,7 @@ const copyObjectClientBaseParams = {

/**
* bucket is appended at start if it's a sourceKey
* guest: public/${targetIdentityId}/${key}`
* guest: public/${key}`
* private: private/${targetIdentityId}/${key}`
* protected: protected/${targetIdentityId}/${key}`
*/
Expand All @@ -52,6 +50,8 @@ const buildClientRequestKey = (
KeyType: 'source' | 'destination',
accessLevel: StorageAccessLevel
) => {
const targetIdentityId = 'targetIdentityId';
const bucket = 'bucket';
const finalAccessLevel = accessLevel == 'guest' ? 'public' : accessLevel;
let finalKey = KeyType == 'source' ? `${bucket}/` : '';
finalKey += `${finalAccessLevel}/`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { Credentials } from '@aws-sdk/types';
import { Amplify, fetchAuthSession } from '@aws-amplify/core';
import { Amplify } from '@aws-amplify/core';
import { getObject } from '../../../../src/providers/s3/utils/client';
import { downloadData } from '../../../../src/providers/s3';
import { createDownloadTask } from '../../../../src/providers/s3/utils';
Expand All @@ -12,6 +12,9 @@ jest.mock('../../../../src/providers/s3/utils');
jest.mock('@aws-amplify/core', () => ({
Amplify: {
getConfig: jest.fn(),
Auth: {
fetchAuthSession: jest.fn(),
},
},
fetchAuthSession: jest.fn(),
}));
Expand All @@ -22,7 +25,7 @@ const credentials: Credentials = {
};
const identityId = 'identityId';

const mockFetchAuthSession = fetchAuthSession as jest.Mock;
const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock;
const mockCreateDownloadTask = createDownloadTask as jest.Mock;
const mockGetConfig = Amplify.getConfig as jest.Mock;

Expand Down
171 changes: 101 additions & 70 deletions packages/storage/__tests__/providers/s3/apis/getProperties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@
import { headObject } from '../../../../src/providers/s3/utils/client';
import { getProperties } from '../../../../src/providers/s3';
import { Credentials } from '@aws-sdk/types';
import { Amplify, fetchAuthSession } from '@aws-amplify/core';
import { Amplify } from '@aws-amplify/core';
import { StorageOptions } from '../../../../src/types';

jest.mock('../../../../src/providers/s3/utils/client');
const mockHeadObject = headObject as jest.Mock;
const mockFetchAuthSession = fetchAuthSession as jest.Mock;
const mockGetConfig = Amplify.getConfig as jest.Mock;

jest.mock('@aws-amplify/core', () => ({
fetchAuthSession: jest.fn(),
Amplify: {
getConfig: jest.fn(),
Auth: {
fetchAuthSession: jest.fn(),
},
},
}));
const mockHeadObject = headObject as jest.Mock;
const mockFetchAuthSession = Amplify.Auth.fetchAuthSession as jest.Mock;
const mockGetConfig = Amplify.getConfig as jest.Mock;

const bucket = 'bucket';
const region = 'region';
Expand All @@ -26,87 +28,116 @@ const credentials: Credentials = {
secretAccessKey: 'secretAccessKey',
};
const targetIdentityId = 'targetIdentityId';
const identityId = 'identityId';

describe('getProperties test', () => {
beforeEach(() => {
jest.clearAllMocks();
});
mockFetchAuthSession.mockResolvedValue({
credentials,
identityId: targetIdentityId,
});
mockGetConfig.mockReturnValue({
Storage: {
S3: {
bucket,
region,
},
},
});
it('getProperties happy path case with private check', async () => {
expect.assertions(3);
mockHeadObject.mockReturnValueOnce({
ContentLength: '100',
ContentType: 'text/plain',
ETag: 'etag',
LastModified: 'last-modified',
Metadata: { key: 'value' },
VersionId: 'version-id',
describe('getProperties api', () => {
beforeAll(() => {
mockFetchAuthSession.mockResolvedValue({
credentials,
identityId,
});
const metadata = { key: 'value' };
expect(
await getProperties({
key: 'key',
options: {
targetIdentityId: 'targetIdentityId',
accessLevel: 'protected',
mockGetConfig.mockReturnValue({
Storage: {
S3: {
bucket,
region,
},
})
).toEqual({
},
});
});
describe('getProperties happy path ', () => {
const expected = {
key: 'key',
size: '100',
contentType: 'text/plain',
eTag: 'etag',
metadata,
metadata: { key: 'value' },
lastModified: 'last-modified',
versionId: 'version-id',
};
const config = {
credentials,
region: 'region',
};
const key = 'key';
beforeEach(() => {
mockHeadObject.mockReturnValueOnce({
ContentLength: '100',
ContentType: 'text/plain',
ETag: 'etag',
LastModified: 'last-modified',
Metadata: { key: 'value' },
VersionId: 'version-id',
});
});
expect(headObject).toBeCalledTimes(1);
expect(headObject).toHaveBeenCalledWith(
afterEach(() => {
jest.clearAllMocks();
});
it.each([
{
options: { accessLevel: 'guest' },
expectedKey: 'public/key',
},
{
credentials,
region: 'region',
options: { accessLevel: 'protected', targetIdentityId },
expectedKey: 'protected/targetIdentityId/key',
},
{
Bucket: 'bucket',
Key: 'protected/targetIdentityId/key',
options: { accessLevel: 'protected' },
expectedKey: 'protected/identityId/key',
},
{
options: { accessLevel: 'private' },
expectedKey: 'private/identityId/key',
},
])(
'getProperties api with $options.accessLevel',
async ({ options, expectedKey }) => {
const headObjectOptions = {
Bucket: 'bucket',
Key: expectedKey,
};
expect.assertions(3);
expect(
await getProperties({
key,
options: options as StorageOptions,
})
).toEqual(expected);
expect(headObject).toBeCalledTimes(1);
expect(headObject).toHaveBeenCalledWith(config, headObjectOptions);
}
);
});

it('getProperties should return a not found error', async () => {
mockHeadObject.mockRejectedValueOnce(
Object.assign(new Error(), {
$metadata: { httpStatusCode: 404 },
name: 'NotFound',
})
);
try {
await getProperties({ key: 'keyed' });
} catch (error) {
expect.assertions(3);
expect(headObject).toBeCalledTimes(1);
expect(headObject).toHaveBeenCalledWith(
{
credentials,
region: 'region',
},
{
Bucket: 'bucket',
Key: 'public/keyed',
}
describe('getProperties error path', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('getProperties should return a not found error', async () => {
mockHeadObject.mockRejectedValueOnce(
Object.assign(new Error(), {
$metadata: { httpStatusCode: 404 },
name: 'NotFound',
})
);
expect(error.$metadata.httpStatusCode).toBe(404);
}
try {
await getProperties({ key: 'keyed' });
} catch (error) {
expect.assertions(3);
expect(headObject).toBeCalledTimes(1);
expect(headObject).toHaveBeenCalledWith(
{
credentials,
region: 'region',
},
{
Bucket: 'bucket',
Key: 'public/keyed',
}
);
expect(error.$metadata.httpStatusCode).toBe(404);
}
});
});
});
Loading

0 comments on commit 0787b12

Please sign in to comment.