Cypress: remove all cookies with intercepted route - javascript

I'm intercepting my login and logout routes in my functional tests with Cypress. (I have to stub them because the Magic technology I'm using for authentication does NOT support a test mode for the server side SDK, yet.)
Here is the code for the routes:
import {
loginRoute,
logoutRoute,
} from 'features/user-authentication/user-authentication-api';
// ...
cy.intercept(loginRoute, request => {
request.reply({
headers: {
'Set-Cookie': `magic-auth-token=${Cypress.env(
'validMagicAuthToken',
)}`,
},
statusCode: 200,
body: { success: true },
});
});
cy.intercept(logoutRoute, request => {
request.reply({
headers: {
'Set-Cookie': `magic-auth-token=; Max-Age=-1; Path=/`,
},
statusCode: 302,
});
});
I'm mimicking the original route's behavior, where they add and remove cookies. The login route's stub works perfectly. However, the stub for the login route does not.
The original logout route looks like this:
import { parse, serialize } from 'cookie';
// ...
function removeTokenCookie<T>(response: NextApiResponse<T>) {
const cookie = serialize(TOKEN_NAME, '', {
maxAge: -1,
path: '/',
});
response.setHeader('Set-Cookie', cookie);
}
const logoutHandler: NextApiHandler = async (request, response) => {
const session = await getSession(request);
if (session) {
await magic.users.logoutByIssuer(session.issuer);
}
removeTokenCookie(response);
response.writeHead(302, { Location: '/' });
response.end();
};
How can I remove the cookies using the logout route's stub? For some reason the cookie does NOT get removed when I set the headers as I did above.

Cypress has the clearCookie command, but it can't be used inside the intercept callback.
cy.intercept(logoutRoute, request => {
cy.clearCookie('magic-auth-token')
request.reply...
})
This is the error
CypressError
Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.
The cy command you invoked inside the promise was: cy.clearCookie()
Looking at the source code for clearCookie, it boils down to the internal command
Cypress.automation('clear:cookie', { name: <cookie-name> })
While it's an internal command, it's use has been demo'd here Cypress Automation and here Testing an Application in Offline Network Mode
The type definitions were added recently Add type for Cypress.automation #7573
Here's a proof of concept,
it('clears cookies in intercept', () => {
cy.setCookie('magic-auth-token', '1234')
cy.getCookies().should('have.length', 1)
cy.intercept('*', (req) => {
Cypress.automation('clear:cookie', { name: 'magic-auth-token' })
})
cy.visit('http://example.com').then(() => {
// after the request has been intercepted
cy.getCookies().should('have.length', 0)
})
})

Related

Dev Server with vite + vue3, route 404 not found

Im in the process of moving an app from Vue 2 -> 3
I decided to take a moment to really upgrade and refactor all my repo and that led to using Vue3 recs on new tech, one being vite
My problem is I don't totally understand how the backend API process works so im struggling to move my api route from vue-cli to vite.
I would like to keep using the logic that call functions from api/users to remain in place but im open to a better option
Ultimately I get 404 - Not Found as my response which means its cant find the route
Heres my api/user.js
import request from '../utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
utils/Request.js
import axios from 'axios'
import { ElMessageBox, ElMessage } from 'element-plus'
import { userStore } from '../stores/user'
import { getToken } from '../utils/auth'
// create an axios instance
const service = axios.create({
baseURL: import.meta.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
const useStore = userStore;
console.log("Req", "Req Init");
if (useStore.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
console.log("failed") // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
console.log("Res", "Res Init");
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
ElMessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
And a peek at my store that actually calls the endpoint
import { login, logout, getInfo } from '../api/user'
actions: { // user login
login({ commit }, userInfo) {
const { username, password } = userInfo
// **Call is made here to 'login'**
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
Lastly where the call originates from: this.store.login in my login.vue component
this.store.login('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
This is my first StackOverflow post, so be kind if I need to include something else and thanks to any ideas or tips. Thank you
I've tried to search on different ports but the route still comes back as undefined.
I tried to change the vite config to include the server option but it still doesn't seem to help.
Not sure If I need to modify the config or not but I did have some settings related to the server mock on my old webpack config

ltijs: Deep Linking hanging when sending back the data to the LMS

I'm loosing my sleep trying to make ltijs work with Deep Linking.
I've built a simple client that registers a tool against an LMS platform. I'm able to setup the tool on the platform (Moodle and Canvas) and perform launches.
The issue I've is when trying to add "Deep Linking" to my tool. Following the directions in the ltjs docs (https://cvmcosta.me/ltijs/#/deeplinking) I've setup an "lti.onDeepLinking()" handler, that redirects to an html page where some content can be selected.
I then post this form to my own tool express server, where its data gets encoded and an auto-submit form is generated and sent back to the client (and then auto-submitted to the LMS).
This is where things "hang". I clearly see the POST request being sent over to the deep_link_redirect_url as requested by the original deep link request message, but the LMS then just "hangs" there until it eventually times out (I guess) and shows a generic error page...
I'm pretty sure I'm missing some vital piece of the puzzle here but I've no clue on what it could be..
const path = require("path");
const lti = require("ltijs").Provider;
const ltiKey = "myverylongandspecialltikeyfortesting";
toolUrl = "http://192.168.1.25:3010";
// setup provider
lti.setup(ltiKey, {
url: "mongodb://127.0.0.1:3001/lticlient",
}, {
// options
appRoute: "/lti/launch",
loginRoute: "/lti/login",
cookies: {
secure: false, // Set secure to true if the testing platform is in a different domain and https is being used
sameSite: "None" // set it to "None" if the testing platform is in a different domain and https is being used
},
devMode: true, // set it to false when in production and using https,
keysetRoute: "/lti/keys",
});
// set the lti launch callback
lti.onConnect((token, req, res) => {
console.log("IDTOKEN", token);
return res.send("LTI TOOL LAUNCHED!");
});
lti.onDeepLinking((token, req, res) => {
console.log("DEEP LINKING", token);
// Call redirect function to deep linking view
lti.redirect(res, '/deeplink')
})
// GET request to show the selection page
lti.app.get("/deeplink", async (req, res) => {
res.sendFile(path.join(__dirname, '/public/select.html'))
});
// POST submit from selection page with selected content item
lti.app.post("/deeplink", async (req, res) => {
const resource = req.body
const items = [
{
type: 'ltiResourceLink',
title: resource.product,
url: `${toolUrl}/lti/launch`,
custom: {
product: resource.product
}
}
]
const form = await lti.DeepLinking.createDeepLinkingForm(res.locals.token, items, { message: 'Successfully registered resource!' })
console.log("RETURNING SELF-SUBMITTING FORM", form);
return res.send(form);
})
const getPlatforms = () => {
return [
{
url: "http://192.168.1.239",
name: "MoodleClient1",
clientId: "client-id-provided-by-Moodle",
authenticationEndpoint: "http://192.168.1.239/mod/lti/auth.php",
accesstokenEndpoint: "http://192.168.1.239/mod/lti/token.php",
authConfig: { method: 'JWK_SET', key: "http://192.168.1.239/mod/lti/certs.php" }
}
];
}
const registerPlatforms = async () => {
const platforms = getPlatforms();
platforms.forEach(async (cfg) => {
console.log(`Registering platform ${cfg.name}`);
await lti.deletePlatform(cfg.url, cfg.clientId);
await lti.registerPlatform(cfg);
const platform = await lti.getPlatform(cfg.url, cfg.clientId);
await platform.platformActive(true)
});
}
const setup = async () => {
await lti.deploy({ port: 3010 });
registerPlatforms();
console.log("platforms registered and active");
}
setup();

Next.js API routes all return "Interval Server Error" in production, but all the routes work in development

Whenever I go to any API route in my Next.js app in production it returns a 500 "Internal Server Error" but in development, all of them work completely fine and show/return what I expect them to.
I am deploying with an AWS Ec2 instance.
the code is available here: https://github.com/123om123/NFT-Marketplace
These are all my API routes.
The [...auth0].js creates the following routes:
/api/auth/login,
/api/auth/logout,
/api/auth/callback, and
/api/auth/me
If I try to access the "find_account" API route like the following:
let findAccount = async function () {
await fetch("/api/find_account", {
method: "POST",
body: JSON.stringify({
DBUrl: DBUrl,
user_id: user.sub,
}),
})
.then(async (response) => {
await response.json().then((result) => {
accountData = result;
if (accountData.data.allAccounts.nodes[0].addresses !== null) {
setAddressList(accountData.data.allAccounts.nodes[0].addresses[0].split(","));
}
});
})
.catch((err) => {
return err;
});
};
which handles requests like the following:
export default function handler(req, res) {
req.body = JSON.parse(req.body);
fetch(req.body.DBUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `query MyQuery {
allAccounts(condition: {userId:"${req.body.user_id}"}) {
nodes {
addresses
}
}
}`,
}),
})
.then((response) => {
response.json().then((response) => {
res.status(200).send(response);
});
})
.catch((err) => {
res.status(500).send(err);
});
}
it works fine and returns the response from the graphql API in development, but in production it shows the above error.
The problem seems to be that the API routes aren't even created and are therefore inaccessible. All the API routes worked a few weeks ago, but now they seem to have stopped working.
Check out this answer, it helped me solve a bug similar to yours. In my case when vercel was deploying my PR branch I was getting a 500 Internal Error. Once I merge the PR into main and vercel does a fresh deployment, I was now getting a 504 gateway error. The answer below helped me find out why.
https://stackoverflow.com/a/68774331/12775824

Different headers used in Axios patch

I spent an hour looking in the Chrome console and I cannot see where this bug comes from.
I am finishing an update of OAuth implementation in my Vue app.
The story begins when socialLink.js finds out that a new user must be created. Vue component Vue-authentication depends on the presence of access_token in a response so I return some dummy text:
return api.sendResponse(res, { email, name, socialId, access_token: 'abcd' });
The library stores this value in localStorage:
After a redirect, the SignUp.vue is rendered and I complete the form. The first communication with the server is a Vuex call to create a new user:
response = await this.$store.dispatch('CREATE_USER_PROFILE', payload);
Which returns a real short lived JWT token:
const token = auth.createToken(userId, nickname, new Date(), null, false, '1m');
return api.sendCreated(res, api.createResponse(token));
Which I store in the Vue page afterwards:
const { data } = response;
const token = data.data;
if (token === undefined) {
this.error = this.$t('sign-up.something-went-wrong');
return false;
}
I checked that the token contains what the server returned:
Request URL: https://beta.mezinamiridici.cz/api/v1/users
Request Method: POST
Status Code: 201 Created
{"success":true,"data":"eyJhbGciOiJIUzI1NiIs...Tl8JFw2HZ3VMXJk"}
Then I call another Vuex method and pass the current JWT token:
await this.$store.dispatch('UPDATE_USER_PROFILE', {
I checked in the Vuex devtools that there really is the correct JWT token. I then pass it further to api.js.
Here I create an Axios configuration holding an Authorization header:
function getAuthHeader(context, jwt = undefined, upload) {
const config = { headers: { } };
if (jwt || (context && context.rootState.users.userToken)) {
config.headers.Authorization = `bearer ${jwt || context.rootState.users.userToken}`;
}
Again, I checked that the correct JWT token is used there.
Finally, I pass all data to Axios:
function patch(endpoint, url, body, context, jwt) {
const headers = getAuthHeader(context, jwt);
console.log(headers);
if (endpoint === 'BFF') {
return axios.patch(`${VUE_APP_BFF_ENDPOINT}${url}`, body, headers);
} else {
return axios.patch(`${VUE_APP_API_ENDPOINT}${url}`, body, headers);
}
}
Which I log and can confirm the correct JWT is still there:
bearer eyJhbGciOiJIUzI1N....8JFw2HZ3VMXJk
There is nothing that could change the header now to abcd, but, the 'Network' tab shows it:
And the server fails with a parse error.
Has anybody got an idea why Axios uses the Authorization header with a different value than I pass it?
Ok, mystery solved. vue-authenticate is the reason, because, it creates Axios interceptors and handles the Authorization header itself.
vue-authenticate.common.js:
var defaultOptions = {
bindRequestInterceptor: function ($auth) {
var tokenHeader = $auth.options.tokenHeader;
$auth.$http.interceptors.request.use(function (config) {
if ($auth.isAuthenticated()) {
config.headers[tokenHeader] = [
$auth.options.tokenType, $auth.getToken()
].join(' ');
} else {
delete config.headers[tokenHeader];
}
return config
});
},
My code is more complex and it supports internal accounts with email/password so this code is breaking mine. The interceptor must be present and be a function, so the solution was:
Vue.use(VueAuthenticate, {
tokenName: 'jwt',
baseUrl: process.env.VUE_APP_API_ENDPOINT,
storageType: 'localStorage',
bindRequestInterceptor() {},
bindResponseInterceptor() {},
providers: {
facebook: {
clientId: process.env.VUE_APP_FACEBOOK_CLIENT_ID,
redirectUri: process.env.VUE_APP_FACEBOOK_REDIRECT_URI,
},

Mock Remote HTTP Response in Tests

I'm attempting to write a test that checks the behaviour of my app depending on the response received from an API. To do this, I'm trying to make the response to the request I make have the config that I need.
An example is checking to see that my app redirects to the home page after logging in. I need the response to be HTTP 200 and have any value for an API key.
I am using axios to make the requests
Currently I have tried the following libraries with no success
moxios
axios-mock-adapter
nock
Does anyone have any experience with mocking remote HTTP requests and their responses?
EDIT: If it helps, I am using Mocha and Chai for tests
it('Does stuff', function () {
moxios.stubRequest('/auth/get_api_key', {
status: 200,
responseText: 'hello'
})
return this.app.client.setValue('[name="username"]', 'testing').then(() => {
return this.app.client.setValue('[name="password"]', 'testing').then(() => {
return this.app.client.submitForm('#login-form').then(() => {
return this.app.client.getRenderProcessLogs().then(function (logs) {
console.log(logs)
})
})
})
})
})
The code above is what I'm using to see if the request goes through and it outputs this
[ { level: 'SEVERE',
message: 'http://127.0.0.1:8000/auth/get_api_key/ - Failed to load resource: net::ERR_CONNECTION_REFUSED',
source: 'network',
timestamp: 1510082077495 } ]
My example was mostly taken from the moxios docs.
First in your test import the libraries you need. Afterwards, you'll handle creating and destroying the mock axios server in your beforeEach and afterEach hooks. You'll then pass a sinon.spy() into axios' get request you want to spy on.
This get request will be mocked by moxios and you can access it through moxios.wait(() => { moxios.request.mostRecent(); }); Inside the wait() method you can set the response you want on the most recent requests.
Afterwards, just like when you use axios you'll use the then() function to get your response. Inside the then() you can create your tests for that specific response. When you're done with your specs call done(); to close the request and move the specs along.
import axios from 'axios';
import moxios from 'moxios';
import sinon from 'sinon';
import { equal } from 'assert'; //use the testing framework of your choice
beforeEach(() => {
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('Does stuff', (done) => {
moxios.withMock(() => {
let onFulfilled = sinon.spy();
axios.get('/auth/get_api_key').then(onFulfilled);
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: {
apiKey: 1234567890
}
}).then((res) => {
equal(onFulfilled.called, true);
equal(res.status, 200);
equal(res.response.apiKey, 1234567890);
done();
})
})
})
});

Categories

Resources