Cypress intercept does not match route with status 204 - cypress

I am implementing cypress tests in our Angular application and have a problem waiting for a request to finish. I am guessing it has to do with the status of the Request being 204 instead of 200.
This is the function/command I am calling in my test:
export function logout() {
cy.intercept('/api/security/logout').as('logoutRequest');
cy.getCookie('SESSION').then((cookie) => {
if (cookie != null) {
cy.request(
{
method: 'POST',
url: '/api/security/logout',
}
);
}
});
cy.wait('#logoutRequest');
}
My problem is, that the route /api/security/logout is not recognized as alias #logoutRequest and therefore the wait always timeouts. Even though there is a vaild request. As you can see here in the test protocol:
I have tried modifing the route with * or ** but without success. I would be very glad if you could help me out.

You can't use cy.intercept() to catch cy.request().
cy.intercept(), cy.server(), and cy.route()
cy.request() sends requests to actual endpoints, bypassing those defined using cy.route() or cy.intercept()
Just chain .then() off the request to handle the reply
cy.request({method: 'POST', url: '/api/security/logout', failOnStatusCode: false})
.then(response => {
expect(response.status).to.eq(200)
})

Related

Calling POST method in serverless offline using Postman

I'm running serverless offline in node.js. When I try hitting the POST endpoint on Postman the request goes on forever and does not seem to call my handler. I'm not sending anything in the request body. It did not make a difference.
Below are examples of the code I'm running
handlers.js file
module.exports.postHandler = async (event, context, callback) => {
console.log("Inside POST Method");
}
Inside serverless.yml
postHandler:
handler: src/handlers.postHandler
events:
- http:
method: post
path: v1/post/handler
I have a GET method setup very similarly. That looks to be working fine
Edit:
I tried sending an empty request on an invalid POST route. Postman still keeps sending the request endless. When I try the same with GET I get the error - Serverless-offline: route not found. Not sure why POST requests do not resolve.
You need to respond to the client in your post request handler, you dont. Return 200 and give an empty json response it will work
Also make sure you are posting to the API POST Route, and giving it data it needs. i.e)
example:
// dont forget content type and content length headers
POST(host, path, { body: { ...data } })
example get:
GET(host, path, "?query=params")
Your current handler
module.exports.postHandler = async (event, context, callback) => {
console.log("Inside POST Method");
}
do,
module.exports.postHandler = async (event, context, callback) => {
context.status = 200;
context.message = "Youre welcome"
}

Cypress MOCK api response for different status

I am testing my login component with Cypress (just started with it) and I want to handle three different cases where the API returns status 200, 400 or 500. I want to mock these responses to see how the frontend responds to that.
I want to mock the response for three different cases (200, 400 and 500) when sending a request to my API endpoint http://localhost:9999/api/login
I have written some code based on the docs but I still am not where I want to be.
describe('Login Approach', () => {
it('login', () => {
cy.visit('/login')
// these values email and pw shouldn't matter if mocking is done right
cy.get('#email')
.type('test')
.should('have.value', 'test')
cy.get('#password')
.type('123456')
.should('have.value', '123456')
cy.server()
cy.route({
method: 'POST',
url: 'http://localhost:9999/api/login', // this is the api that I send the request to
})
cy.location('pathname', { timeout: 10000 }).should('eq', '/login');
cy.title().should('include', 'Condeo')
cy.get('#notification').should('exist')
})
})
I am not getting status in the details of the test:
Method Url Stubbed Alias #
POST http://localhost:9999/api/login Yes -
You should use the wait method of cypress.
You can find the cypress documentation here.
For your use case, make sure you start the server and define the route before you visit the link. Just after visiting the link, use the cy.wait() method which will wait for that API call to finish.
Eg.
describe('Login Approach', () => {
it('login', () => {
cy.visit('/login')
// these values email and pw shouldn't matter if mocking is done right
cy.get('#email')
.type('test')
.should('have.value', 'test')
cy.get('#password')
.type('123456')
.should('have.value', '123456')
cy.server()
cy.route({
method: 'POST',
url: 'http://localhost:9999/api/login', // this is the api that I send the request to
}).as('login')
cy.location('pathname', { timeout: 10000 }).should('eq', '/login');
cy.title().should('include', 'Condeo')
cy.get('#notification').should('exist')
// Code which will try to visit the login API.
cy.wait('#login').then((xhr)=> {
if(xhr.status === 200) {
// Code to test when status is 200
} else if(xhr.status === 400) {
// Code to test when status is 400
} else {
// Code to test when status is none of the above.
}
})
})
})

How to wait for a successful response in Cypress tests

Background
I use 3 back-end servers to provide fault tolerance for one of my online SaaS application. All important API calls, such as getting user data, contact all 3 servers and use value of first successfully resolved response, if any.
export function getSuccessValueOrThrow$<T>(
observables$: Observable<T>[],
tryUntilMillies = 30000,
): Observable<T> {
return race(
...observables$.map(observable$ => {
return observable$.pipe(
timeout(tryUntilMillies),
catchError(err => {
return of(err).pipe(delay(5000), mergeMap(_err => throwError(_err)));
}),
);
})
);
}
getSuccessValueOrThrow$ get called as following:
const shuffledApiDomainList = ['server1-domain', 'server2-domain', 'server3-domain';
const sessionInfo = await RequestUtils.getSuccessValueOrThrow(
...(shuffledApiDomainList.map(shuffledDomain => this.http.get<SessionDetails>(`${shuffledDomain}/file/converter/comm/session/info`))),
).toPromise();
Note: if one request resolve faster than others, usually the case, race rxjs function will cancel the other two requests. On Chrome dev network tab it will look like bellow where first request sent out was cancelled due to being too slow.
Question:
I use /file/converter/comm/session/info (lets call it Endpoint 1) to get some data related to a user. This request dispatched to all 3 back-end servers. If one resolve, then remaining 2 request will be cancelled, i.e. they will return null.
On my Cypress E2E test I have
cy.route('GET', '/file/converter/comm/session/info').as('getSessionInfo');
cy.visit('https://www.ps2pdf.com/compress-mp4');
cy.wait('#getSessionInfo').its('status').should('eq', 200)
This sometimes fails if the since getSessionInfo alias was hooked on to a request that ultimately get cancelled by getSuccessValueOrThrow$ because it wasn't the request that succeeded.Bellow image shows how 1 out of 3 request aliased with getSessionInfo succeeded but the test failed since the first request failed.
In Cypress, how do I wait for a successful i.e. status = 200 request?
Approach 1
Use .should() callback and repeat the cy.wait call if status was not 200:
function waitFor200(routeAlias, retries = 2) {
cy.wait(routeAlias).then(xhr => {
if (xhr.status === 200) return // OK
else if (retries > 0) waitFor200(routeAlias, retries - 1); // wait for the next response
else throw "All requests returned non-200 response";
})
}
// Usage example.
// Note that no assertions are chained here,
// the check has been performed inside this function already.
waitFor200('#getSessionInfo');
// Proceed with your test
cy.get('button').click(); // ...
Approach 2
Revise what it is that you want to test in the first place.
Chances are - there is something on the page that tells the user about a successful operation. E.g. show/hide a spinner or a progress bar, or just that the page content is updated to show new data fetched from the backend.
So in this approach you would remove cy.wait() altogether, and focus on what the user sees on the page - do some assertions on the actual page content.
cy.wait() yields an object containing the HTTP request and response properties of the XHR. The error you're getting is because you're looking for property status in the XHR object, but it is a property of the Response Object. You first have to get to the Response Object:
cy.wait('#getSessionInfo').should(xhr => {
expect(xhr.response).to.have.property('status', 200);
});
Edit: Since our backend uses graphql, all calls use the single /graphql endpoint. So I had to come up with a solution to differentiate each call.
I did that by using the onResponse() method of cy.route() and accumulating the data in Cypress environment object:
cy.route({
method: 'GET',
url: '/file/converter/comm/session/info',
onResponse(xhr) {
if (xhr.status === 200) {
Cypress.env('sessionInfo200') = xhr;
}
}
})
You can then use it like this:
cy.wrap(Cypress.env()).should('have.property', 'sessionInfo200');
I wait like this:
const isOk = cy.wait("#getSessionInfo").then((xhr) => {
return (xhr.status === 200);
});

Axios Reponse Interceptor : unable to handle an expired refresh_token (401)

I have the following interceptor on my axios reponse :
window.axios.interceptors.response.use(
response => {
return response;
},
error => {
let errorResponse = error.response;
if (errorResponse.status === 401 && errorResponse.config && !errorResponse.config.__isRetryRequest) {
return this._getAuthToken()
.then(response => {
this.setToken(response.data.access_token, response.data.refresh_token);
errorResponse.config.__isRetryRequest = true;
errorResponse.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return window.axios(errorResponse.config);
}).catch(error => {
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
The _getAuthToken method is :
_getAuthToken() {
if (!this.authTokenRequest) {
this.authTokenRequest = window.axios.post('/api/refresh_token', {
'refresh_token': localStorage.getItem('refresh_token')
});
this.authTokenRequest.then(response => {
this.authTokenRequest = null;
}).catch(error => {
this.authTokenRequest = null;
});
}
return this.authTokenRequest;
}
The code is heavily inspired by https://github.com/axios/axios/issues/266#issuecomment-335420598.
Summary : when the user makes a call to the API and if his access_token has expired (a 401 code is returned by the API) the app calls the /api/refresh_token endpoint to get a new access_token. If the refresh_token is still valid when making this call, everything works fine : I get a new access_token and a new refresh_token and the initial API call requested by the user is made again and returned correctly.
The problem occurs when the refresh_token has also expired.
In that case, the call to /api/refresh_token returns a 401 and nothing happens. I tried several things but I'm unable to detect that in order to redirect the user to the login page of the app.
I found that in that case the if (!this.authTokenRequest) statement inside the _getAuthToken method returns a pending Promise that is never resolved. I don't understand why this is a Promise. In my opinion it should be null...
I'm a newbie with Promises so I may be missing something !
Thanks for any help !
EDIT :
I may have found a way much simpler to handle this : use axios.interceptors.response.eject() to disable the interceptor when I call the /api/refresh_token endpoint, and re-enable it after.
The code :
createAxiosResponseInterceptor() {
this.axiosResponseInterceptor = window.axios.interceptors.response.use(
response => {
return response;
},
error => {
let errorResponse = error.response;
if (errorResponse.status === 401) {
window.axios.interceptors.response.eject(this.axiosResponseInterceptor);
return window.axios.post('/api/refresh_token', {
'refresh_token': this._getToken('refresh_token')
}).then(response => {
this.setToken(response.data.access_token, response.data.refresh_token);
errorResponse.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
this.createAxiosResponseInterceptor();
return window.axios(errorResponse.config);
}).catch(error => {
this.destroyToken();
this.createAxiosResponseInterceptor();
this.router.push('/login');
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
},
Does it looks good or bad ? Any advice or comment appreciated.
Your last solution looks not bad. I would come up with the similar implementation as you if I were in the same situation.
I found that in that case the if (!this.authTokenRequest) statement inside the _getAuthToken method returns a pending Promise that is never resolved. I don't understand why this is a Promise. In my opinion it should be null...
That's because this.authTokenRequest in the code was just assigned the Promise created from window.axios.post. Promise is an object handling kind of lazy evaluation, so the process you implement in then is not executed until the Promise was resolved.
JavaScript provides us with Promise object as kind of asynchronous event handlers which enables us to implement process as then chain which is going to be executed in respond with the result of asynchronous result. HTTP requests are always inpredictable, because HTTP request sometimes consumes much more time we expect, and also sometimes not. Promise is always used when we use HTTP request in order to handle the asynchronous response of it with event handlers.
In ES2015 syntax, you can implement functions with async/await syntax to hanle Promise objects as it looks synchronous.

Angular Js - Test POST request that returns an object with Jasmine

I have a service set up which makes all my AJAX calls. I want to test my login method, which sends an AJAX POST $http.post to a particular URL, which returns an objects with the result (login passed or failed). This result is an object. I have not return the code exactly to test the service, but I'm trying to test the URL first. This is how it looks right now:
'use strict';
describe('Service: Ajax', function () {
var service, httpBackend;
// load the service's module
beforeEach(module('mySampleApp'));
// instantiate service
beforeEach(inject(function (Ajax, _$httpBackend_) {
httpBackend = _$httpBackend_;
service = Ajax;
}));
it('Test AJAX call', function () {
httpBackend.expect('POST', 'http://myloginurl', {u: 'xyz', p: 'pass'}, { withCredentials: true})
.respond(201, 'success');
});
});
This passes. Now I tried putting a wrong URL, wrong username/password, but it still passes! How do I test this?
UPDATE:
Better written now:
//Ajax is my AJAX service
it('should test the login AJAX call', inject(function (Ajax) {
httpBackend.expect('POST', 'http://myloginurl')
.respond(200, "[{ status: 200, //some more data }]");
httpBackend.flush();
Ajax.authenticate({u: 'xyz', password: 'pass' })
.then(function(data){
expect(data.status).toBe(200);
});
}));
I get this:
PhantomJS 1.9.7 (Linux) Service: Ajax should test the login AJAX call FAILED
Error: No pending request to flush !
blah blah...
You need to put a
httpBackend.flush();
in that will throw an exception if the expected url wasn't called - thereby failing your test.
Also, I can't see that you're calling the code that does the Ajax request anywhere - you need to do that before calling flush().
So something like:
it('Test AJAX call', function () {
httpBackend.expect('POST', 'http://myloginurl', {u: 'xyz', p: 'pass'}, { withCredentials: true})
.respond(201, 'success');
service.functionThatMakesCallToServer();
httpBackend.flush();
});
If functionThatMakesCallToServer() calls the url in the httpBackend.expect(...) line, everything will be ok. If it doesn't httpBackend.flush() will throw an error as a call that was expected did not happen. The error will cause your test to fail.

Resources