How i can CATCH any error (network and graphql), and prevent promise.reject()? - apollo

i Already can get Apollo Client errors:
import {onError} from 'apollo-link-error'
function createLink() {
return ApolloLink.from([
createErrorLink(),
createHttpLink(),
])
}
function createErrorLink() {
return onError(function ({graphQLErrors, networkError}) {
if (graphQLErrors) {
graphQLErrors.map(({message}) => {
console.log(`[GraphQL error]`, message)
})
}
if (networkError) {
console.log(`[Network error]`, networkError)
}
})
}
Yes, error handlers works, but how i can PREVENT promise rejection?

const errorLink = onError(({networkError, graphQLErrors, response}) => {
// DO ANYTHING
// Stop error propagation to next Links
response.errors = null
})
HttpLink not see errors end not reject promise =)

Related

How to handle errors in websocket in jest end to end test?

I have one file which runs server on start. And there is one function to upgrade the connection based on some checks of the request.
//A.ts
export abstract class A{
protected _server: http.Server;
protected _wss: WebSocket.Server;
constructor(){
this._wss = new WebSocket.Server({ noServer: true });
}
async start(port: number) {
await this.startServer(port);
await this.startUpgardeListener();
await this.startWsConnectionListener();
}
private async startServer(port: number) {
this._server.listen(port, () => {
Logger.log(`Server started on port ` + port);
});
}
private async startUpgardeListener() {
this._server.on("upgrade", async (request: http.IncomingMessage, clientSocket, head: Buffer) => {
return await this.upgradeHandler(request, clientSocket as Socket, head);
});
}
private async startWsConnectionListener() {
this._wss.on('connection', async (clientWebSocket: WebSocket, request: http.IncomingMessage) => {
return await this.connectionHandler(clientWebSocket, request);
});
}
protected abstract upgradeHandler(request: http.IncomingMessage, clientSocket: Socket, head: Buffer): Promise<void>;
protected abstract connectionHandler(clientWebSocket: WebSocket, request: http.IncomingMessage): void;
}
//B.ts
export class FESProxy extends Proxy {
private _c = new H();
protected async upgradeHandler(request: IncomingMessage, clientTcpSocket: Socket, head: Buffer): Promise<void> {
let resource;
try{
this.initHandlers();
this.create(resource);
}
catch(e){
resource = this.cleanup(clientTcpSocket, resource);
}
}
private async create(resource){
resource = await this._c.createResource(resource);
if(resource.status === 400)
{
error = new Error(400);
throw error;
}
return true;
}
private initHandlers(resource: Resource, clientTcpSocket: Socket) {
this.initOnClose(clientTcpSocket, resource);
this.initOnError(clientTcpSocket, resource);
}
private initOnClose(clientTcpSocket: Socket, resource: Resource | undefined) {
clientTcpSocket.on('close', (err) => {
console.log(`Client TCP Socket Close Handler`);
resource = this.cleanup(clientTcpSocket, resource);
});
return resource;
}
private initOnError(clientTcpSocket: Socket, resource: Resource | undefined) {
clientTcpSocket.on('error', (e) => {
Logger.error(`Client TCP Socket Error Handler);
resource = this.cleanup(clientTcpSocket, resource);
});
return resource;
}
}
// B.spec.ts
let b : B;
let wb : WebSocket;
let workerServer : WebSocketServer;
describe("B test", () => {
beforeEach(async () => {
b = new B();
await b.start(3000);
})
afterEach(async () => {
wb.close();
workerServer.close();
b["_server"]?.close(()=>{});
});
it("Should handle 400 error in create call", async () => {
let resource = new Resource();
resource.status = 400;
const spiedOncreate = await jest.spyOn(b["_c"],"createResource").mockReturnValue(Promise.resolve(resource));
let uri = 'ws://localhost:3000/ws';
wb = new WebSocket(uri);
wb.on('error' ,(err) => {
console.log(err);
})
wb.on('close',() => {
})
wb.on('message',(e) =>{
console.log("Message in the socket ",e);
})
expect(spiedOncreate).toBeCalledTimes(1);
})
})
On running the test, I am getting this message
Expected number of calls: 1
Received number of calls: 0
Although I checked that catch block was called in the upgradeHandler method of B class with the status code of resource as 400. Which means that the createResource was called and with the status 400 which I set in the test only.
I also found that on error handler of websocket inside the test got triggered and got the message in the console
Error: Unexpected server response: 400
I am not able to see any other console logs if I put after assertion statement in the test. I guess my test is getting crashed and the error is not handled but not sure here. I added handlers in the test for websockets but still not working.
What am I missing or doing wrong to assert on the spyOn as I am not receiving the expected number of calls?

jest.spyOn mock return value not returning value

The code I'm trying to test:
const utils = require('../utils/utils');
let imageBuffer;
try {
imageBuffer = await utils.retrieveImageFromURI(params)
console.log(imageBuffer) // comes back as undefined when I mock the utils.retreieveImageFromURI
if (!imageBuffer || imageBuffer.length < 1024) {
throw new Error(`Retrieve from uri (${params.camera.ingest.uri}) was less than 1kb in size - indicating an error`)
}
console.log(`${params.camera.camId} - Successful Ingestion from URI`);
} catch (err) {
reject({ 'Task': `Attempting to pull image from camera (${params.camera.camId}) at ${params.camera.ingest.uri}`, 'Error': err.message, 'Stack': err.stack })
return;
}
Specifically, I'm trying to mock the utils.retrieveImageFromURI function - which has API calls and other things in it.
When I try to mock the function using spyOn I am trying it like so:
describe("FUNCTION: ingestAndSave", () => {
let fakeImageBuffer = Array(1200).fill('a').join('b'); // just get a long string
console.log(fakeImageBuffer.length) //2399
let retrieveImageFromURISpy
beforeAll(() => {
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(fakeImageBuffer)
})
test("Will call retrieveImageFromURI", async () => {
await ingest.ingestAndSave({camera:TEST_CONSTANTS.validCameraObject, sourceQueueURL:"httpexamplecom", receiptHandle: "1234abcd"})
expect(retrieveImageFromURISpy).toHaveBeenCalledTimes(1)
})
afterEach(() => {
jest.resetAllMocks()
})
afterAll(() => {
jest.restoreAllMocks()
})
})
When I do this, I get a console log that imageBuffer (which is supposed to be the return of the mocked function) is undefined and that, in turn, triggers the thrown Error that "Retrieve from uri ...." ... which causes my test to fail. I know I could wrap the test call in a try/catch but the very next test will be a "does not throw error" test... so this needs to be solved.
It's not clear to me why the mockReturnValue isn't getting returned.
Other steps:
I've gone to the REAL retrieveImageFromURI function and added a console log - it is not running.
I've changed mockReturnValue to mockImplementation like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return fakeImageBuffer
})
And it does NOT console log 'here'. I'm unsure why not.
I have also tried to return it as a resolved Promise, like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return Promise.resolve(fakeImageBuffer)
})
Note, this also doesn't console log.
I've also tried to return the promise directly with a mockReturnValue:
`retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(Promise.resolve(fakeImageBuffer)`)

react-native AWS PUBSUB unable to reconect

I have a network action in my app, that fires fetchUser every time the app reconects to the internet.
I have another saga that looks as follows
export function* watchNewMessage() {
const { id = {} } = yield take(SET_USER_ID)
const channel = yield call(subscribeToNewMessages, id)
while (true) {
const message = yield take(channel)
console.log(message)
}
}
Now the problem is when the app disconects i stop receiving messages which would be fine because my reconect should refire the saga function, but because its stuck in a while true loop its never waiting to be reset. So how do I change the code to reset every time SET_USER_ID is called?
Here is the rest of the relevant code
export function subscribeToNewMessages(id) {
return eventChannel(emit => {
const pubsub = fetchNewMessage(emit, id)
return () => {
pubsub.unsubscribe()
}
}, buffers.expanding(100))
}
export const fetchNewMessage = async (emit, id) => {
return PubSub.subscribe(`${IOT_PREFIX}/discussion/${id}/message`).subscribe({
next: data => { emit(data.value) },
error: () => console.log('error'),
close: () => console.log('close'),
})
}
Okay I got this working
I created a new function
export function* watchNewMessage() {
yield takeLatest(SET_USER_ID, manageSubscription)
}
export function* manageSubscription() {
const { id = {} } = yield take(SET_USER_ID)
const channel = yield call(subscribeToNewMessages, id)
while (true) {
const message = yield take(channel)
console.log(message)
}
}
And now it works perfectly

How to return error response in apollo link?

I'm using apollo link in schema stitching as an access control layer. I'm not quite sure how to make the link return error response if a user does not have permissions to access a particular operation. I know about such packages as graphql-shield and graphql-middleware but I'm curious whether it's possible to achieve basic access control using apollo link.
Here's what my link looks like:
const link = setContext((request, previousContext) => merge({
headers: {
...headers,
context: `${JSON.stringify(previousContext.graphqlContext ? _.omit(previousContext.graphqlContext, ['logger', 'models']) : {})}`,
},
})).concat(middlewareLink).concat(new HttpLink({ uri, fetch }));
The middlewareLink has checkPermissions that returns true of false depending on user's role
const middlewareLink = new ApolloLink((operation, forward) => {
const { operationName } = operation;
if (operationName !== 'IntrospectionQuery') {
const { variables } = operation;
const context = operation.getContext().graphqlContext;
const hasAccess = checkPermissions({ operationName, context, variables });
if (!hasAccess) {
// ...
}
}
return forward(operation);
});
What should I do if hasAccess is false. I guess I don't need to forward the operation as at this point it's clear that a user does not have access to it
UPDATE
I guess what I need to do is to extend the ApolloLink class, but so far I didn't manage to return error
Don't know if anyone else needs this, but I was trying to get a NetworkError specifically in the onError callback using Typescript and React. Finally got this working:
const testLink = new ApolloLink((operation, forward) => {
let fetchResult: FetchResult = {
errors: [] // put GraphQL errors here
}
let linkResult = Observable.of(fetchResult).map(_ => {
throw new Error('This is a network error in ApolloClient'); // throw Network errors here
});
return linkResult;
});
Return GraphQL errors in the observable FetchResult response, while throwing an error in the observable callback will produce a NetworkError
After some digging I've actually figured it out. But I'm not quite sure if my approach is correct.
Basically, I've called forward with a subsequent map where I return an object containing errors and data fields. Again, I guess there's a better way of doing this (maybe by extending the ApolloLink class)
const middlewareLink = new ApolloLink((operation, forward) => {
const { operationName } = operation;
if (operationName !== 'IntrospectionQuery') {
const { variables } = operation;
const context = operation.getContext().graphqlContext;
try {
checkPermissions({ operationName, context, variables });
} catch (err) {
return forward(operation).map(() => {
const error = new ForbiddenError('Access denied');
return { errors: [error], data: null };
});
}
}
return forward(operation);
});

ReactJs - test multiple calls in redux-saga with expectSaga

I'm using expectSaga ('redux-saga-test-plan') to test one of my sagas and I'm wondering how to test multiple calls made within the same saga.
Sagas.js
export function* fetchSomething(arg){
const response = yield call(executeFetch, arg);
if(response.status === 200){
// trigger success action
} else if (response.status >= 400){
const errResp = yield response.json();
const errorCode = yield call(sharedUtilToExtractErrors, errResp);
yield put(
{ type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, errorCode }
);
}
}
Unit test
import { expectSaga } from 'redux-saga-test-plan';
describe('fetchSomething', () => {
// positive paths
// ..
// negative paths
it('fetches something and with status code 400 triggers FETCH_FAILED with error message and extracted error code', () => {
const serverError = { message: 'BANG BANG BABY!' };
const koResponse = new Response(
JSON.stringify(serverError),
{ status: 400, headers: { 'Content-type': 'application/json' } }
);
return expectSaga(fetchSomething)
.provide(
{
call: () => koResponse,
call: () => serverError.message,
}
)
.put({
type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, serverError.message
})
.run();
})
})
Clearly having the "call" attribute twice in the same object passed in to provide() doesn't work but also calling provide() twice doesn't do the trick. Any suggestions?
Thanks
This is how you can provide multiple calls according to the documentation:
.provide([ // this external array is actually optional
[call(executeFetch, arg), koResponse],
[call(sharedUtilToExtractErrors, serverError), serverError.message],
])
or if you're lazy and don't want to specify the arguments:
import * as matchers from 'redux-saga-test-plan/matchers';
.provide(
[matchers.call.fn(executeFetch), koResponse],
[matchers.call.fn(sharedUtilToExtractErrrors), serverError.message],
)
Neither of these two worked for me though as for some reason it was not mocking out the dependencies and still calling them caused errors.
I solved using a dynamic provider:
.provide({
// select(effect, next) { return 'something-for-a-selector' },
call(effect) {
switch(effect.fn.constructor.name) {
case executeFetch.constructor.name: return koResponse;
case sharedUtilToExtractErrors.constructor.name: return serverError.message;
default: throw new Error('Unknown function called in test');
}
}
})