One of my ideas would be to overwrite the request command, but I don't know how to handle the response object.
A snippet I already have:
Cypress.Commands.overwrite(
'request',
(
originalFn: Cypress.CommandOriginalFn<'request'>,
options: Partial<Cypress.RequestOptions>
): void | Cypress.Chainable<Cypress.Response<unknown>> => {
return originalFn(options);
}
);
My other idea would be to intercept all requests, but there are already interceptors added and you can not have two for one request.
beforeEach(() => {
cy.intercept(
{
url: '*/**',
},
req => {
// tried with 'after:response' too
req.on('response', res => {
cy.log(`${res.headers['x-custom-header']}`);
});
}
);
});
Is there any other way to log a custom header value for all request?
My final working solution was to add this code to /support/index.ts
beforeEach(() => {
cy.intercept({ url: '*', middleware: true }, req => {
req.on('after:response', (res => {
const customHeaderKey = 'x-custom-header';
const customHeaderValue = res.headers[customHeaderKey];
if (customHeaderValue) {
const message = JSON.stringify({ [customHeaderKey]: customHeaderValue });
Cypress.log({ message }).finish();
}
}));
});
});
Related
I've got a graphql URI that I need to query from my react-native App. This URI is public and I've got access to its schema/structure when I simply type the URI in my browser.
As soon as I try to query it from my code, I get the [TypeError: Network request failed] error (logs are created in the function that builds my ApolloClient).
I've checked the URI a million time, it's the same as the one I put in my browser, and the one I've used in the past to successfully query the DB.
This is the client-building function:
export function initServices({
uri,
authToken,
mockMeanDelay = 400,
mock = false,
mockScenarios = [],
}: Options): Services {
let mockRemoteController = null;
let linkToOutsideWorld: ApolloLink;
const messageBus = createMessageBus();
const terminatingLink = createUploadLink({
uri: CORRECT_URI_HERE,
})
const authLink = setContext(async (_, { headers }) => {
const token = await authToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
linkToOutsideWorld = from([authLink, withCustomScalars(), terminatingLink]);
const errorReportingLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
// eslint-disable-next-line no-console
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
// eslint-disable-next-line no-console
if (networkError) console.error(`[Network error]: ${networkError}`);
});
const link = ApolloLink.from([errorReportingLink, linkToOutsideWorld]);
const fragmentMatcher = new IntrospectionFragmentMatcher({
// #ts-ignore
introspectionQueryResultData: introspectionResult,
});
const apolloClient = new ApolloClient({
link,
defaultOptions: {
watchQuery: {
// We prefer using a `cache-and-network` policy so that screens
// are always in sync with backend
// Otherwise, the default policy would not fetch the server
// data from server if the result of query is already in cache
fetchPolicy: 'cache-and-network',
},
},
cache: new InMemoryCache({
cacheRedirects: {
Query: {
// #ts-ignore issue in typing of cacheRedirects
userById: (_, { userId }: QueryUserByIdArgs, { getCacheKey }) =>
getCacheKey({ __typename: 'User', id: userId }),
// #ts-ignore issue in typing of cacheRedirects
gatheringSpaceById: (
_,
{ gatheringSpaceId }: QueryGatheringSpaceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringSpace',
id: gatheringSpaceId,
}),
// #ts-ignore issue in typing of cacheRedirects
gatheringInstanceById: (
_,
{ gatheringInstanceId }: QueryGatheringInstanceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringInstance',
id: gatheringInstanceId,
}),
},
},
fragmentMatcher,
}),
});
return { apolloClient, messageBus, mockRemoteController };
}
When I replace the URI with another publicly available one, it seems to work so my guess is that there's an issue with the back-end side. But how is it possible that I have full access to the schema and queries with my browser?
Any tips to help debugging are welcome too!
Thanks for your help!
I am new to HttpTesting controller. my expectations not working as the url contains HttpParams.
here is my service, and spec.ts
hasAccessControlException() {
const apiPath = `https://api.example.com/v1/session/permissions/exceptions/hasAccessControlException/`;
let params = new HttpParams();
params = params.append('auditInstanceId', 123);
return this.http
.get<boolean>(apiPath, {
params: params,
observe: 'response'
})
.pipe(
map((response) => {
return response.body;
})
);
}
describe('PermissionExceptionService', () => {
let httpTestingController: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
PermissionExceptionService,
]
});
service = TestBed.inject(PermissionExceptionService);
httpTestingController = TestBed.inject(HttpTestingController);
})
fit('should access control', () => {
service.hasAccessControlException();
const url = `https://api.example.com/v1/session/permissions/exceptions/hasAccessControlException/`;
const request = httpTestingController.expectOne((req) => {
expect(req.url).toBe(url);
return true;
});
req.flush(null);
httpTestingController.verify();
})
});
I am getting the following error:
Error: Expected one matching request for criteria "Match by function: ", found none
The above code worked, when the service call is subscribed. i.e
service.hasAccessControlException().subscribe();
I know some questions about the subject has been opened here and there, but my issue is different :
all the other ones appear in dev mode, in my case it's in production,
a very big percentage of requests pass, a few of them is TypeError: Network request failed - but sometimes for critical requests
it's random, not always the same request. Sometimes it passes, sometimes not.
it appears to three on my projects, one is on AWS the other one on Clever-Cloud, both are projects between 1000 and 5000 users, servers are quite too big for what they do - I think I removed the risk of a server fault. Even if... I can reproduce locally when I don't start the api locally. So it's like the api is not responding, but as I said, I don't think so.
I have no clue where to dig anymore...
I can give you my API.js service file, maybe you'll find what's wrong ?
import URI from 'urijs';
import { Platform } from 'react-native';
import NetInfo from '#react-native-community/netinfo';
import { getUserToken, wipeData } from '../utils/data';
import { SCHEME, MW_API_HOST } from '../config';
import deviceInfoModule from 'react-native-device-info';
import { capture } from '../utils/sentry';
const unauthorisedHandler = (navigation) => {
wipeData();
navigation.reset({ index: 0, routes: [{ name: 'Auth' }] });
};
const checkNetwork = async (test = false) => {
const isConnected = await NetInfo.fetch().then((state) => state.isConnected);
if (!isConnected || test) {
await new Promise((res) => setTimeout(res, 1500));
return false;
}
return true;
};
class ApiService {
host = MW_API_HOST;
scheme = SCHEME;
getUrl = (path, query) => {
return new URI().host(this.host).scheme(this.scheme).path(path).setSearch(query).toString();
};
execute = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
try {
const config = {
method,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
appversion: deviceInfoModule.getBuildNumber(),
appdevice: Platform.OS,
currentroute: this.navigation?.getCurrentRoute?.()?.name,
...headers,
},
body: body ? JSON.stringify(body) : null,
};
const url = this.getUrl(path, query);
console.log('url: ', url);
const canFetch = await checkNetwork();
if (!canFetch) return;
let response;
// To try to avoid mysterious `TypeError: Network request failed` error
// that throws an error directly
// we try catch and try one more time.
try {
response = await fetch(url, config);
} catch (e) {
if (e?.toString().includes('Network request failed')) {
// try again
await new Promise((res) => setTimeout(res, 250));
console.log('try again because Network request failed');
response = await fetch(url, config);
} else {
throw e;
}
}
if (!response.ok) {
if (response.status === 401) {
const token = await getUserToken();
if (token) unauthorisedHandler(API.navigation);
return response;
}
}
if (response.json) return await response.json();
return response;
} catch (e) {
capture(e, { extra: { method, path, query, headers, body } });
return { ok: false, error: "Sorry, an error occured, technical team has been warned." };
}
};
executeWithToken = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
const token = await getUserToken();
if (token) headers.Authorization = token;
return this.execute({ method, path, query, headers, body });
};
get = async (args) => this.executeWithToken({ method: 'GET', ...args });
post = async (args) => this.executeWithToken({ method: 'POST', ...args });
put = async (args) => this.executeWithToken({ method: 'PUT', ...args });
delete = async (args) => this.executeWithToken({ method: 'DELETE', ...args });
}
const API = new ApiService();
export default API;
Talking with experts here and there, it seems that it's normal : internet network is not 100% reliable, so sometimes, request fail, for a reason that we can't anticipate (tunnel, whatever).
I ended up using fetch-retry and I still have a few of those, but much less !
I have this authentication issue
The registration works perfectly and the servers take the registration data: User password email and number. After this step, I have OTP verification
I got the pin code and run the verification mutation.
On the verification, I got the error :
You are not authenticated
And the all process stops because I am not verified
Here is the code for the react-native part
const VERIFY = gql
mutation($token: String!, $kind: TokenKind!) {
verify(token: $token, kind: $kind)
}
const VerificationScreen: React.FC < any > = (props) => {
const token = (props as any).route.params.token;
const [loading, setLoading] = React.useState < boolean > (false)
const [pin, setPin] = useState < string > ('')
const [veryfy] = useMutation(VERIFY)
const verifyPin = () => {
if (!pin) {
alert('Please TYPE Valid PIN')
return;
}
//veryfy
setLoading(true);
veryfy({
variables: {
token: pin,
kind: 'PHONE'
}
}).then(({
data
}) => {
setLoading(false)
console.log(data);
if (data) {
props.navigation.navigate('Intro', {
token: token
});
}
}).catch((e) => {
setLoading(false)
console.log(e);
})
}
The below code is an example showing how you can use the Apollo middle-ware [1] and context [2] to add headers(auth) at runtime or testing.
First we create a middle-ware block, then an inner context block.
In the context block we can use the line below to add external parameters, this is to configure the request
const { isAuth, Authorization } = headers;
A boolean(Auth) can be set to allow a token to be embedded in a Authorization header, or an existing Authorization header can be passed in directly, this can be usefull for testing for example.
const getClient = () => {
// create link middleware see [1]
const authMiddleware = new ApolloLink((operation, forward) => {
// code block below assumes there exists an auth token in globals
// add headers with the client context [2]
operation.setContext(({ headers = {} }) => {
// auth header using global token as a bearer token
const authHeaders = {
Authorization: global.access_token
? `Bearer ${global.access_token}`
: "",
};
// here an Authorization header can be passed in thru the context,
// which can be useful, eg for testing
const { isAuth, Authorization } = headers;
// if we have an Auth.. header we can just add that and return
if (Authorization) {
return {
headers: { ...publicHeaders, ...{ Authorization } },
};
}
const header = isAuth
? { ...publicHeaders, ...authHeaders }
: publicHeaders;
return {
headers: header,
};
});
return forward(operation);
}); // end create middleware
// create the graphql endpoint [1]
const httpLink = new HttpLink({ uri: '/graphql' });
// create client with the middleware and return the client
// code block below assumes there exists a globalCache
return new ApolloClient({
cache: globalCache,
link: concat(authMiddleware, httpLink),
});
}
use
// add/configure the appropriate headers in the context block
// for the client
client
.mutate({
mutation: <some mutation>,
variables: <some variables>,
context: {
headers: {
isAuth: false, // or true for authenticated operation
},
},
})
.then((result) => ....)
.catch((err) => {
console.log(....);
});
use in a hook
const [runMutation, { data }] =
useMutation(<gql>, {
context: {
headers: { isAuth: true },
variables: <some vars>,
onCompleted: (data) => callback(data),
onError: (error) => console.error("Error ", error),
},
});
links
1 middleware
2 context
I making a multi-upload file form.
Upon user cancellation, once the corresponding axios call get cancelled using cancel(), I having a weird behaviour. My axios call get caught inside the then() whereas it should be caught inside of catch(). The response inside of then() returns undefined.
I am having a hard time figuring if I did something wrong on the front-end part, I think my call is may be missing some headers or maybe it's on the backend part ?
const payload = { file, objectId: articleId, contentType: 'article' };
const source = axios.CancelToken.source();
// callback to execute at progression
const onUploadProgress = (event) => {
const percentage = Math.round((100 * event.loaded) / event.total);
this.handleFileUploadProgression(file, {
percentage,
status: 'pending',
cancelSource: source,
});
};
attachmentService
.create(payload, { onUploadProgress, cancelToken: source.token })
.then((response) => {
// cancelation response ends up here with a `undefined` response content
})
.catch((error) => {
console.log(error);
// canceled request do not reads as errors down here
if (axios.isCancel(error)) {
console.log('axios request cancelled', error);
}
});
the service itself is defined below
export const attachmentService = {
create(payload, requestOptions) {
// FormData cannot be decamelized inside an interceptor so it's done before, here.
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) =>
formData.append(decamelize(key), value),
);
return api
.post(resource, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...requestOptions,
})
.then((response) => {
console.log(response, 'cancelled request answered here as `undefined`');
return response.data;
})
.catch((error) => {
// not caught here (earlier)
return error.data;
});
},
};
cancellation is called upon a file object doing
file.cancelSource.cancel('Request was cancelled by the user');
As suggested by #estus-flask in a comment, the issue is that I was catching the error inside of the service (too early). Thank you!
export const articleService = {
create(payload, requestOptions) {
// FormData cannot be decamelized inside an interceptor so it's done before, here.
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) =>
formData.append(decamelize(key), value),
);
return api.post(resource, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...requestOptions,
});
},
};