How do I wait until a cookie is set? - javascript

I am writing the acceptance tests for my application's login feature. At some point, I want to double-check the cookie's expiry time.
Upon clicking on the "Login" button, a graphql query is sent to my server which responds with a Jwt. Upon reception of the jwt, the application sets the cookie with
document.cookie = ...
In my Cypress test, I check the token in the following way:
Then("sa session s'ouvre pour {SessionDurationType}", expectedDuration => {
cy.get('#graphql').then(() => {
cy.wait(1000)
cy.getCookie('token').then(cookie => {
const tokenDuration = getTokenDuration(cookie.value)
expect(tokenDuration.asSeconds()).to.equal(expectedDuration.asSeconds())
})
})
})
With cy.get('#graphql'), I am waiting for the graphql query to return a response. The alias is defined like this:
cy.stub(win, 'fetch', fetch).as('graphql')
Upon reception, the application sets the cookie.
My problem is that I am not fond of the following call:
cy.wait(1000)
Without that call, I always get an undefined cookie.
Is there a way to get that cookie within some time that might be much less than 1000 ms? I tried many things without success...

You must write a recursive promise function, try the following
function checkCookie() {
// cy.getCookie returns a thenebale
return cy.getCookie('token').then(cookie => {
const tokenDuration = getTokenDuration(cookie.value);
// it checks the seconds right now, without unnecessary waitings
if(tokenDuration.asSeconds() !== expectedDuration.asSeconds()) {
// waits for a fixed milliseconds amount
cy.wait(100);
// returns the same function recursively, the next `.then()` will be the checkCookie function itself
return checkCookie();
}
// only when the condition passes returns a resolving promise
return Promise.resolve(tokenDuration.asSeconds());
})
}
Then("sa session s'ouvre pour {SessionDurationType}", expectedDuration => {
cy.get('#graphql').then(() => {
checkCookie()
.then(seconds => {
expect(seconds).to.equal(expectedDuration.asSeconds())
})
})
})
Note that the function must be improved because
I didn't parametrize the expectedDuration etc. (it's out of the scope of showing you how to do that)
it waits forever without a loop counter check
But it works (I checked in another context before replying to you) and if you have some more troubles please share a "working" GitHub repo so I can clone and check it with your own solution.
Let me know if it isn't enough clear ๐Ÿ˜‰
UPDATE
We (me and Tommaso) have written a plugin to help you with this kind of checks, its name is cypress-wait-until.
Please thank the Open Source Saturday community for that, we developed it during one of them Saturdays ๐Ÿ˜Š

I dont like the timeout in this i have to say for dom changes. I have come up with this solution based on #NoriSte Answer together with DomMutation Observers.
getFileUploadItem().get(".upload-item--state i")
.should("have.class", "ngx-fileupload-icon--start")
.then(item => {
const iconEl = item.get(0);
const states: string[] = [];
return new Promise((resolve, reject) => {
const observer = new MutationObserver((mutations: MutationRecord[]) => {
const mutationEl = mutations[0].target as HTMLElement;
const className = mutationEl.getAttribute("class");
states.push(className);
if (className === "ngx-fileupload-icon--uploaded") {
resolve(states);
}
});
observer.observe(iconEl, {
subtree: true,
attributes: true,
attributeFilter: ["class"]
});
});
})
.then((value) => expect(value).to.deep.equal(
["ngx-fileupload-icon--progress", "ngx-fileupload-icon--uploaded"])
);

Based on #NoriSte's answer, I came up with the following working code:
function awaitNonNullToken(elapsedTimeInMs = 0) {
let timeDeltaInMs = 10
if (elapsedTimeInMs > Cypress.env('timeoutInMs')) {
return Promise.reject(new Error('Awaiting token timeout'))
}
return getTokenCookie().then(cookie => {
if (cookie === null) {
cy.wait(timeDeltaInMs)
elapsedTimeInMs += timeDeltaInMs
return awaitNonNullToken(elapsedTimeInMs)
}
return Promise.resolve(cookie.value)
})
}
I transformed that into an ES6 class that I find a bit more elegant:
class TokenHandler {
constructor () {
this.TIME_DELTA_IN_MS = Cypress.env('timeDeltaInMs')
this.TIMEOUT_IN_MS = Cypress.env('timeoutInMs')
this.elapsedTimeInMs = 0
}
getToken () {
if (this.elapsedTimeInMs > this.TIMEOUT_IN_MS) {
return Promise.reject(new Error('Awaiting token timeout'))
}
return getTokenCookie().then(cookie => {
if (cookie === null) {
cy.wait(this.TIME_DELTA_IN_MS)
this.elapsedTimeInMs += this.TIME_DELTA_IN_MS
return this.getToken()
}
return Promise.resolve(cookie.value)
})
}
}
and reworked my step like this:
cy.get('#graphql').then(() => {
const handler = new TokenHandler
handler.getToken().then(token => {
const tokenDuration = getTokenDuration(token)
expect(tokenDuration.asSeconds()).to.equal(expectedDuration.asSeconds())
})
})
This is working perfectly, thanks.

Related

Javascript code is running in a limited loop, need to refactor

I have a set of code that is working as intended, but my PR approver is telling me to refactor because it's running in a loop, and there should be a more efficient way to write the code. I'm still learning javascript, so often problems like this throw me for a loop (no pun intended), and I rely on the greater world of the internet for learning/help.
The code is meant to check a URL's domain against an array of 'valid' domains and return whether the URL's domain is valid (returns false) or invalid (returns true).
How do I go about refactoring this code so there is no loop in calling cleanupParams?
export const isValidLink = (hostname?: string, validDomains: string[] = []) => {
return validDomains.every((domain) => {
if (hostname && !hostname.startsWith("#")) {
return cleanupParams(hostname).indexOf(domain) < 0;
}
});
};
const cleanupParams = (url: string) => {
let domain = url;
try {
domain = new URL(url).hostname;
} catch {
domain = url;
}
return domain;
};
The method every will run the callback method for each item in the array. The hostname, though, is not affected by the array , and so, should not need to be cleaned up each time. You should do that, once, outside the every loop.
export const isValidLink = (hostname?: string, validDomains: string[] = []) => {
// early break if conditions are not met
const isValidHostname = hostname && !hostname.startsWith("#");
if (!isValidHostname) return false;
const cleanedUpHostname = cleanupParams(hostname);
return validDomains.every((domain) => {
return cleanedUpHostname.indexOf(domain) < 0;
});
};

if else in loop bringing up errors in typescript

I have this function that is supposed to get referral codes from users. User gives a code and the referral code checked if it exists in the database then evaluated if
it does not match the current user, so that one should not refer himself and
it is a match with one of the codes in the database
This code however just does not find a match even if the code given is in the database. If the referral code matches the one of the current user, it works correctly and points that out i.e one cannot refer themselves.
But if the referral code is a match to that of another user which is how a referral system should work, it still says no match.
How can I remove this error
export const getID = functions.https.onCall(async(data, context) => {
const db = admin.firestore();
const usersSnapshot = await db.collection("user").get();
const allUIDs = usersSnapshot.docs.map(doc => doc.data().userID);
const userID = context.auth.uid;
const providedID = "cNx7IuY6rZlR9mYSfb1hY7ROFY2";
//db.collection("user").doc(providedID).collection("referrals").doc(userID);
await check();
function check() {
let result;
allUIDs.forEach(idFromDb => {
if (providedID === idFromDb && (idFromDb === userID)) {
result = "ownmatch";
} else if (providedID === idFromDb && (idFromDb !== userID)) {
result = "match";
} else {
result = "nomatch";
}
});
return result;
}
if (check() === "match") {
return {
message: `Match Found`,
};
} else if (check() === "ownmatch") {
return {
message: `Sorry, you can't use your own invite code`,
};
} else {
return {
message: `No User with that ID`
};
}
});
(This is not an answer, but a simple refactoring.)
This is what your code is currently doing (roughly, I didn't run it):
const resultMsgs = {
nomatch: 'No User With That ID',
ownmatch: 'Sorry, you can\'t use your own invite code',
match: 'Match Found',
}
function check(uids, providedId, userId) {
let result
uids.forEach(idFromDb => {
if (providedId !== idFromDb) {
result = 'nomatch'
return
}
if (userID === idFromDb) {
result = 'ownmatch'
return
}
result = 'match'
})
return result
}
export const getID = functions
.https
.onCall(async (data, context) => {
const userId = context.auth.uid
const providedId = 'cNx7IuY6rZlR9mYSfb1hY7ROFY2'
const db = admin.firestore()
const user = await db.collection('user').get()
const uids = user.docs.map(doc => doc.data().userId)
const checkResult = check(uids, providedId, userId)
return { message: resultMsgs[checkResult] }
})
(I removed the seemingly-spurious db collection operation.)
Your forEach is iterating over all of the uuids, but result will be set to whatever the last comparison was. Perhaps this is correct, but:
If you're looking for any match, this is not what you want.
If you're looking for all matches, this is not what you want.
If you're looking to match the last UUID, it's what you want, but an odd way to go about it.
So:
If you want any matches, use... ahem any form of an any function.
If you want all matches, use any form of an all function.
If you want the first match, then just check the first element.
If you want the complete set of comparisons then you'll need to use map instead of forEach, and handle each result appropriately, whatever that means in your case.
In any event, I'd recommend breaking up your code more cleanly. It'll be much easier to reason about, and fix.

Cloud Function triggered, but not executed

I have following function:
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
const original = change.after.val();
return change.after.ref('/Lager/Shafts/Rescue/583/583001').set(original);
});
I am trying to keep the count of product 1 equal to the count of product two (Can't put it in the same ID for several reasons). It executes the function and says the status is ok but does not update the new value of product 2.
What am I missing?
Please try this, Your function is exiting without executing the update.
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.after.exists()) {
const original = change.after.val();
return admin.database().ref('/Lager/Shafts/Rescue/583/583001').set(original);
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
});
This seems like a noop:
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.before.exists()) {
return null;
}
Or more precisely: it will only get past this code when you delete /Lager/Shafts/Rescue/582/582001, which is not what you seem to be trying. My guess is that you meant the inverse in your check:
if (!change.before.exists()) {
return null;
}

node.js resolve promise and return value

I use the Microsoft bot framework to come up with a "simple" PoC bot. I used a tutorial as a basis and extend it.
I've a couple of basic functions for differet intents (ie. greetings, goodbye, etc) and one with some more logic in it (reqstatus).
The simple ones (ie greeting.js) return the answer nicely but the more complex one doesn't (reqstatus.js). Running the main code of reqstatus.js (without the first "const getReqStatus = (entity) => {") in a standalone script works.
server.js (main) -> see call in "if (intent) {"...
const getFeelings = require('./intents/feelings.js')
const getGoodbyes = require('./intents/goodbyes.js')
const getGreetings = require('./intents/greetings.js')
const getHelp = require('./intents/help.js')
const getReqStatus = require('./intents/reqstatus.js')
...
const bot = new builder.UniversalBot(connector)
// Intents based on definitions on recast
const INTENTS = {
feelings: getFeelings,
goodbyes: getGoodbyes,
greetings: getGreetings,
help: getHelp,
reqstatus: getReqStatus,
}
// Event when Message received
bot.dialog('/', (session) => {
recastClient.textRequest(session.message.text)
.then(res => {
const intent = res.intent()
const entity = res.get('request_number')
console.log(`UserName: ${session.message.user.name}`)
console.log(`Msg: ${session.message.text}`)
console.log(`Intent: ${intent.slug}`)
if (intent) {
INTENTS[intent.slug](entity)
.then(res => session.send(res))
.catch(err => session.send(err))
}
})
.catch(() => session.send('Sorry I didn\'t get that. '))
})
...
greetings.js -> Returns the string ok
const getGreetings = () => {
const answers = ['Hi, my name is SuperBot. Nice to meet you!', ]
return Promise.resolve((answers))
}
module.exports = getGreetings
reqstatus.js -> Does not return anything
const getReqStatus = (entity) => {
var request = require('request');
var request_number = entity.toLowerCase()
var output = [];
// Processing
var lineReader = require('readline').createInterface({
input: fs.createReadStream('netreqs.csv')
});
lineReader.on('line', function (line) {
var jsonFromLine = {};
var lineSplit = line.split(';');
jsonFromLine.req = lineSplit[0];
jsonFromLine.req_count = lineSplit[1];
jsonFromLine.req_type = lineSplit[2];
//...
var req_lowever = jsonFromLine.req.toLowerCase()
if (req_lowever == request_number) {
output.push( `Your request ${jsonFromLine.req} was received`);
// simplified
}
});
// Output
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return Promise.resolve(output);
});
}
module.exports = getReqStatus
I also tried to put getReqStatus in a function but that also didn't work.
After a lot of trying and googling I'm still stuck and wanted to ask the experts here. Thanks a lot in advance.
I think that the problem is that your getReqStatus isn't really returning anything. In your example getGreetings function you're actually returning Promise.resolve(answers) as the return value of that function.
However, in your getReqStatus function, you just set up a listener lineReader close event:
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return Promise.resolve(output);
});
You're returning a Promise resolved inside the anonymous callback function you're passing to lineReader.on() as second parameter. That is not the return value from the getReqStatus function itself, so that getReqStatus is not returning anything, as expected.
The code of that function runs correctly as standalone code, as you say, just because it sets the listener properly and it does what it has to do. However, that code just doesn't return a Promise when wrapped in a function.
What you would need is to return a Promise that wraps the lineReader.on close handler, like:
function getReqStatus(){
//...code
return new Promise( function(resolve , reject ){
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return resolve(output);
});
});
}
I say would because I really don't know if this code will work, I don't have any kind of experience with the Microsoft Bot framework and not used at all with the readline module. However, even if this doesn't solve your problem, I hope it will help you a bit understanding why your function doesn't return a Promise and how could you fix it.

What's the best(right) way to write a polling method (with Typescript & AngularJS)?

I am trying to write a polling method that polls a server periodically to check whether a zip file has already been created or not.
What I want to accomplish are the following:
Calls(ajax) an API that creates a zip file on server
Calls(ajax) another API that checks if the zip file has already been created (polling method)
Some subsequent process
Here is my code snippet โ†“
var success: boolean = false;
//1. requests a server to create a zip file
this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
.then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.status[0].statusCode == "000") {
success = true;
} else {
//Error
}
}).then(() => {
if (success) {
//2. polls the server to check if the zip file is ready
<- Polling methodโ†“ ->
this.polling(params).then((zipUrl) => {
console.log(zipUrl); //always logs zipUrl
//some subsequent process...
});
}
});
Could anyone give some examples of polling method that would work in this case?
Added:
private polling(params: any): ng.IPromise<any> {
var poller = () => this.apiRequest.polling(params, ApiUrl.URL_FOR_POLLING);
var continuation = () => poller().then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.zipFilePath == "") {
return this.$timeout(continuation, 1000);
} else {
return apiRes.zipFilePath;
}
})
var result: ng.IPromise<any> = continuation();
return result;
}
Basically abstract the methods out as shown below:
let poll = () => this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
let continuation = () => poll().then((/*something*/)=> {
/*if still bad*/ return continuation();
/*else */ return good;
})
continuation().then((/*definitely good*/));
Update
As requested in the comment below:
return this.$timeout(continuation, 1000);
This is needed to get angular to kick off a digest cycle.

Categories