Skip to content
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

release(required): Amplify JS release #13971

Merged
merged 8 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@

# The following paths involve server-side use cases, token/user session management.
# Changes made to these paths requires additional reviews and approvals.
/packages/auth @haverchuck @cshfang @jimblanc @HuiSF
/packages/aws-amplify/src/adapter-core @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/adapterCore @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/singleton @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/utils/convert @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/utils/WordArray.ts @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/storage @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/utils/generateRandomString.ts @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/utils/globalHelpers @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/utils/urlSafeDecode.ts @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/awsClients/cognitoIdentity @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/clients/internal @haverchuck @cshfang @jimblanc @HuiSF
/packages/core/src/Hub @haverchuck @cshfang @jimblanc @HuiSF
/packages/adapter-nextjs @haverchuck @cshfang @jimblanc @HuiSF
/packages/rtn-web-browser @haverchuck @cshfang @jimblanc @HuiSF
/packages/storage/src/providers/s3/apis/internal @haverchuck @cshfang @jimblanc @HuiSF
/packages/storage/src/providers/s3/apis/server @haverchuck @cshfang @jimblanc @HuiSF
/packages/api-rest/src/apis/server.ts @haverchuck @cshfang @jimblanc @HuiSF
/packages/api-rest/src/apis/common/internalPost.ts @haverchuck @cshfang @jimblanc @HuiSF
/packages/api-graphql/src/server @haverchuck @cshfang @jimblanc @HuiSF
/packages/api-graphql/src/internals/server @haverchuck @cshfang @jimblanc @HuiSF
/packages/auth @haverchuck @cshfang @HuiSF @pranavosu
/packages/aws-amplify/src/adapter-core @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/adapterCore @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/singleton @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/utils/convert @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/utils/WordArray.ts @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/storage @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/utils/generateRandomString.ts @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/utils/globalHelpers @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/utils/urlSafeDecode.ts @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/awsClients/cognitoIdentity @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/clients/internal @haverchuck @cshfang @HuiSF @pranavosu
/packages/core/src/Hub @haverchuck @cshfang @HuiSF @pranavosu
/packages/adapter-nextjs @haverchuck @cshfang @HuiSF @pranavosu
/packages/rtn-web-browser @haverchuck @cshfang @HuiSF @pranavosu
/packages/storage/src/providers/s3/apis/internal @haverchuck @cshfang @HuiSF @pranavosu
/packages/storage/src/providers/s3/apis/server @haverchuck @cshfang @HuiSF @pranavosu
/packages/api-rest/src/apis/server.ts @haverchuck @cshfang @HuiSF @pranavosu
/packages/api-rest/src/apis/common/internalPost.ts @haverchuck @cshfang @HuiSF @pranavosu
/packages/api-graphql/src/server @haverchuck @cshfang @HuiSF @pranavosu
/packages/api-graphql/src/internals/server @haverchuck @cshfang @HuiSF @pranavosu
177 changes: 177 additions & 0 deletions packages/api-graphql/__tests__/AWSAppSyncEventProvider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { Observable, Observer } from 'rxjs';
import { Reachability } from '@aws-amplify/core/internals/utils';
import { ConsoleLogger } from '@aws-amplify/core';
import { MESSAGE_TYPES } from '../src/Providers/constants';
import * as constants from '../src/Providers/constants';

import { delay, FakeWebSocketInterface } from './helpers';
import { ConnectionState as CS } from '../src/types/PubSub';

import { AWSAppSyncEventProvider } from '../src/Providers/AWSAppSyncEventsProvider';

describe('AppSyncEventProvider', () => {
describe('subscribe()', () => {
describe('returned observer', () => {
describe('connection logic with mocked websocket', () => {
let fakeWebSocketInterface: FakeWebSocketInterface;
const loggerSpy: jest.SpyInstance = jest.spyOn(
ConsoleLogger.prototype,
'_log',
);

let provider: AWSAppSyncEventProvider;
let reachabilityObserver: Observer<{ online: boolean }>;

beforeEach(async () => {
// Set the network to "online" for these tests
jest
.spyOn(Reachability.prototype, 'networkMonitor')
.mockImplementationOnce(() => {
return new Observable(observer => {
reachabilityObserver = observer;
});
})
// Twice because we subscribe to get the initial state then again to monitor reachability
.mockImplementationOnce(() => {
return new Observable(observer => {
reachabilityObserver = observer;
});
});

fakeWebSocketInterface = new FakeWebSocketInterface();
provider = new AWSAppSyncEventProvider();

// Saving this spy and resetting it by hand causes badness
// Saving it causes new websockets to be reachable across past tests that have not fully closed
// Resetting it proactively causes those same past tests to be dealing with null while they reach a settled state
jest
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket as WebSocket;
});

// Reduce retry delay for tests to 100ms
Object.defineProperty(constants, 'MAX_DELAY_MS', {
value: 100,
});
// Reduce retry delay for tests to 100ms
Object.defineProperty(constants, 'RECONNECT_DELAY', {
value: 100,
});
});

afterEach(async () => {
provider?.close();
await fakeWebSocketInterface?.closeInterface();
fakeWebSocketInterface?.teardown();
loggerSpy.mockClear();
});

test('subscription observer error is triggered when a connection is formed and a non-retriable connection_error data message is received', async () => {
expect.assertions(3);

const socketCloseSpy = jest.spyOn(
fakeWebSocketInterface.webSocket,
'close',
);
fakeWebSocketInterface.webSocket.readyState = WebSocket.OPEN;

const observer = provider.subscribe({
appSyncGraphqlEndpoint: 'ws://localhost:8080',
});

observer.subscribe({
error: e => {
expect(e.errors[0].message).toEqual(
'Connection failed: UnauthorizedException',
);
},
});

await fakeWebSocketInterface?.readyForUse;
await fakeWebSocketInterface?.triggerOpen();

// Resolve the message delivery actions
await Promise.resolve(
fakeWebSocketInterface?.sendDataMessage({
type: MESSAGE_TYPES.GQL_CONNECTION_ERROR,
errors: [
{
errorType: 'UnauthorizedException', // - non-retriable
errorCode: 401,
},
],
}),
);

// Watching for raised exception to be caught and logged
expect(loggerSpy).toHaveBeenCalledWith(
'DEBUG',
expect.stringContaining('error on bound '),
expect.objectContaining({
message: expect.stringMatching('UnauthorizedException'),
}),
);

await delay(1);

expect(socketCloseSpy).toHaveBeenCalledWith(3001);
});

test('subscription observer error is not triggered when a connection is formed and a retriable connection_error data message is received', async () => {
expect.assertions(2);

const observer = provider.subscribe({
appSyncGraphqlEndpoint: 'ws://localhost:8080',
});

observer.subscribe({
error: x => {},
});

const openSocketAttempt = async () => {
await fakeWebSocketInterface?.readyForUse;
await fakeWebSocketInterface?.triggerOpen();

// Resolve the message delivery actions
await Promise.resolve(
fakeWebSocketInterface?.sendDataMessage({
type: MESSAGE_TYPES.GQL_CONNECTION_ERROR,
errors: [
{
errorType: 'Retriable Test',
errorCode: 408, // Request timed out - retriable
},
],
}),
);
await fakeWebSocketInterface?.resetWebsocket();
};

// Go through two connection attempts to excercise backoff and retriable raise
await openSocketAttempt();
await openSocketAttempt();

// Watching for raised exception to be caught and logged
expect(loggerSpy).toHaveBeenCalledWith(
'DEBUG',
expect.stringContaining('error on bound '),
expect.objectContaining({
message: expect.stringMatching('Retriable Test'),
}),
);

await fakeWebSocketInterface?.waitUntilConnectionStateIn([
CS.ConnectionDisrupted,
]);

expect(loggerSpy).toHaveBeenCalledWith(
'DEBUG',
'Connection failed: Retriable Test',
);
});
});
});
});
});
50 changes: 25 additions & 25 deletions packages/api-graphql/__tests__/AWSAppSyncRealTimeProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { ConnectionState as CS } from '../src/types/PubSub';

import { AWSAppSyncRealTimeProvider } from '../src/Providers/AWSAppSyncRealTimeProvider';
import { isCustomDomain } from '../src/Providers/AWSWebSocketProvider/appsyncUrl';

// Mock all calls to signRequest
jest.mock('@aws-amplify/core/internals/aws-client-utils', () => {
Expand All @@ -20,7 +21,7 @@ jest.mock('@aws-amplify/core/internals/aws-client-utils', () => {
);
return {
...original,
signRequest: (_request, _options) => {
signRequest: (_request: any, _options: any) => {
return {
method: 'test',
headers: { test: 'test' },
Expand All @@ -46,7 +47,7 @@ jest.mock('@aws-amplify/core', () => {
};
return {
...original,
fetchAuthSession: (_request, _options) => {
fetchAuthSession: (_request: any, _options: any) => {
return Promise.resolve(session);
},
Amplify: {
Expand All @@ -66,24 +67,19 @@ jest.mock('@aws-amplify/core', () => {
describe('AWSAppSyncRealTimeProvider', () => {
describe('isCustomDomain()', () => {
test('Custom domain returns `true`', () => {
const provider = new AWSAppSyncRealTimeProvider();
const result = (provider as any).isCustomDomain(
'https://unit-test.testurl.com/graphql',
);
const result = isCustomDomain('https://unit-test.testurl.com/graphql');
expect(result).toBe(true);
});

test('Non-custom domain returns `false`', () => {
const provider = new AWSAppSyncRealTimeProvider();
const result = (provider as any).isCustomDomain(
const result = isCustomDomain(
'https://12345678901234567890123456.appsync-api.us-west-2.amazonaws.com/graphql',
);
expect(result).toBe(false);
});

test('Non-custom domain in the amazonaws.com.cn subdomain space returns `false`', () => {
const provider = new AWSAppSyncRealTimeProvider();
const result = (provider as any).isCustomDomain(
const result = isCustomDomain(
'https://12345678901234567890123456.appsync-api.cn-north-1.amazonaws.com.cn/graphql',
);
expect(result).toBe(false);
Expand Down Expand Up @@ -136,10 +132,12 @@ describe('AWSAppSyncRealTimeProvider', () => {
// Saving this spy and resetting it by hand causes badness
// Saving it causes new websockets to be reachable across past tests that have not fully closed
// Resetting it proactively causes those same past tests to be dealing with null while they reach a settled state
jest.spyOn(provider, 'getNewWebSocket').mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket as WebSocket;
});
jest
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket as WebSocket;
});

// Reduce retry delay for tests to 100ms
Object.defineProperty(constants, 'MAX_DELAY_MS', {
Expand Down Expand Up @@ -228,7 +226,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
expect.assertions(1);

const newSocketSpy = jest
.spyOn(provider, 'getNewWebSocket')
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket as WebSocket;
Expand All @@ -254,7 +252,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
expect.assertions(1);

const newSocketSpy = jest
.spyOn(provider, 'getNewWebSocket')
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket as WebSocket;
Expand All @@ -280,7 +278,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
expect.assertions(1);

const newSocketSpy = jest
.spyOn(provider, 'getNewWebSocket')
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket as WebSocket;
Expand All @@ -307,7 +305,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
expect.assertions(1);

const newSocketSpy = jest
.spyOn(provider, 'getNewWebSocket')
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket;
Expand Down Expand Up @@ -349,7 +347,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
expect.assertions(1);

const newSocketSpy = jest
.spyOn(provider, 'getNewWebSocket')
.spyOn(provider as any, '_getNewWebSocket')
.mockImplementation(() => {
fakeWebSocketInterface.newWebSocket();
return fakeWebSocketInterface.webSocket;
Expand Down Expand Up @@ -545,7 +543,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
await fakeWebSocketInterface?.standardConnectionHandshake();

await fakeWebSocketInterface?.sendDataMessage({
type: MESSAGE_TYPES.GQL_DATA,
type: MESSAGE_TYPES.DATA,
payload: { data: {} },
});

Expand All @@ -571,7 +569,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
connectionTimeoutMs: 100,
});
await fakeWebSocketInterface?.sendDataMessage({
type: MESSAGE_TYPES.GQL_DATA,
type: MESSAGE_TYPES.DATA,
payload: { data: {} },
});

Expand All @@ -597,7 +595,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
connectionTimeoutMs: 100,
});
await fakeWebSocketInterface?.sendDataMessage({
type: MESSAGE_TYPES.GQL_DATA,
type: MESSAGE_TYPES.DATA,
payload: { data: {} },
});
expect(mockNext).toHaveBeenCalled();
Expand Down Expand Up @@ -677,7 +675,9 @@ describe('AWSAppSyncRealTimeProvider', () => {
}),
);

expect(socketCloseSpy).toHaveBeenNthCalledWith(1, 3001);
await delay(1);

expect(socketCloseSpy).toHaveBeenCalledWith(3001);
});

test('subscription observer error is triggered when a connection is formed', async () => {
Expand Down Expand Up @@ -931,7 +931,7 @@ describe('AWSAppSyncRealTimeProvider', () => {

await fakeWebSocketInterface?.standardConnectionHandshake();
await fakeWebSocketInterface?.sendDataMessage({
type: MESSAGE_TYPES.GQL_DATA,
type: MESSAGE_TYPES.DATA,
payload: { data: {} },
});
await subscription.unsubscribe();
Expand Down Expand Up @@ -1181,7 +1181,7 @@ describe('AWSAppSyncRealTimeProvider', () => {
});

test('authenticating with AWS_LAMBDA/custom w/ custom header function that accepts request options', async () => {
expect.assertions(2);
expect.assertions(3);

provider
.subscribe({
Expand Down
Loading
Loading