-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix events client should throw non-retryable errors
- Loading branch information
Showing
5 changed files
with
241 additions
and
15 deletions.
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
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