-
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
fix(api-graphql): events url pattern; non-retryable error handling #13970
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
fade122
fix(api-graphql): fix events url pattern
iartemiev 3093d23
fix events client should throw non-retryable errors
iartemiev 1599401
bump bundle size
iartemiev 0e9a4f5
undo pre-release
iartemiev 2e38945
Merge branch 'main' into feat/events/main
iartemiev 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
177 changes: 177 additions & 0 deletions
177
packages/api-graphql/__tests__/AWSAppSyncEventProvider.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,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', | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
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,14 @@ | ||
import { getRealtimeEndpointUrl } from '../src/Providers/AWSWebSocketProvider/appsyncUrl'; | ||
|
||
describe('getRealtimeEndpointUrl', () => { | ||
test('events', () => { | ||
const httpUrl = | ||
'https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event'; | ||
|
||
const res = getRealtimeEndpointUrl(httpUrl).toString(); | ||
|
||
expect(res).toEqual( | ||
'wss://abcdefghijklmnopqrstuvwxyz.appsync-realtime-api.us-east-1.amazonaws.com/event/realtime', | ||
); | ||
}); | ||
}); |
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
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
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.
These are duplicated from AppSyncRealtimeProvider tests, but the tests include the event-specific error response shapes to test retryable/non-retryable error handling