How Testcafe handle login errors? - javascript

I'm testing a website that needs authentication with different users. It's working most of the time but sometimes, login fails and Testcafé doesn't detect it before running into the actual test code. Rather than raising an error into the login method, it fails when finding a DOM element in the test page. So it keeps the wrong login information and other tests with the same user will fail too.
I know the way to detect a login error on my website but I can't say to Testcafé:
"Hey! Something wrong appends when login, don't save login information for this user and try again in next tests"
EDIT:
Rather than using hardcoded login information, I use a separate file logins.ts with the following structure and I adapt it to add loggedIn and role fields :
adminUserCredentials: { login: 'mylogin', pwd: 'mypass', role: null, loggedIn: false }
Then I use it as follow:
function createUserForSpecificEnv(user: User, baseUrl: string): Role {
if(!user.loggedIn) {
user.role = Role(baseUrl, async t => {
await t
.wait(1000)
.typeText('#loginInput', user.login)
.typeText('#passwordInput', user.pwd)
.click('#Btn')
if(await Selector('#user-info').visible) {
user.loggedIn = true
}
})
}
return user.role
}
const adminUserRole = getRole(adminUserCredentials)
test("test 1", async t => {
t.useRole(adminUserRole)
}) // The test go on the login page, auth failed (expected) and can't find #user-info (expected)
test("test 2", async t => {
t.useRole(adminUserRole)
}) // The test doesn't go to the login page and directly says : can't find #user-info
But it's still not working... TestCafe tries to log in on the first test and then it directly reuses the same login information.
Any help would be appreciated! Thank you :-)
EDIT 2
I clarify the fact that I use variable to store the role (see comments)

If login fail is expected I'd suggest not using Role for this particular test. You can extract an authentication logiс into a separate function and use it in this test directly and in the Role constructor:
const logIn = async t => {
//login steps
};
user.role = Role(baseUrl, logIn);
test("test 1", async t => {
await logIn(t);
}); // The test go on the login page, auth failed (expected) and can't find #user-info (expected)
test("test 2", async t => {
t.useRole(adminUserRole)
});

I think you can check some Selector on the last step of Role initialization to make sure that you are correctly logged in. If you are not, you need to recreate your role for further usage in the next tests. Please see the following code, which demonstrates my idea:
import { Role, Selector } from 'testcafe';
let role = null;
let loggedIn = false;
function getRole() {
if (!loggedIn) {
role = new Role('http://example.com', async t => {
console.log('role initialize');
// await t.typeText('#login', 'login');
// await t.typeText('#password', 'password');
// await t.click('#signin');
// NOTE: ensure that we are actually logged in
if(await Selector('#user_profile').exists)
loggedIn = true;
});
}
return role;
}
fixture `fixture`
.page `../pages/index.html`;
test(`test1`, async t => {
await t.useRole(getRole());
});
test(`test2`, async t => {
await t.useRole(getRole());
});
test(`test3`, async t => {
await t.useRole(getRole());
});

Related

How can I remove bookmarked posts of user (1) from user (2) tab after user (1) deletes his account?

After creating a node.js, express, mongoDb REST api for a social media web app with almost all basic social media actions (login, signup, add a post, delete a post, delete account, follow users ...),
I'm currently facing a problem, where after implementing bookmarking a post feature, I'm unable to come up with a solution to remove a bookmarked post from another user's bookmarked posts page, after the first user deletes his account. I'll provide my code below:
(P. S. Bookmarks is an array inside User model. I'd also like to mention the steps that I initially intended for the task:
Get current user by ID
Then get all posts created by this user, which returns an array, so I mapped it to get each Posts id
After that I fetched all users accross the app, and initially intended to compare the posts that live inside bookmarks array inside each user to the posts that the current user have created. Then I'd pull these same posts out of the bookmarks array from each of these users.
--> I think the logic that I've analyzed is maintainable, but it's just not working with me. This is the Code below:
export const deleteUser = async (req, res) => {
try {
let user = await User.findById(req.params.userId)
const userPosts = await Post.find({ creatorId: user._id })
const allUsers = await User.find()
const myPostsIds = userPosts.map((post) => post._id.toString())
//This is the section I've implemented for my task, but obviously
something isn't right
await Promise.all(
myPostsIds.forEach((id) =>
allUsers.map((user) => {
user.bookmarks.includes(id) &&
user.updateOne({ $pull: { bookmarks: id } })
})
)
)
await Post.deleteMany({ creatorId: user._id })
await user.remove()
res.status(200).json({
message: "Account has been deleted successfully!",
})
} catch (err) {
errorHandler(res, err)
}
}
As mentioned in my comments, the value you pass to Promise.all is no array of Promise/array of async functions.
The 2nd error is inside the (currently) forEach function at the .map() you are not returning anything in the map-call.
So this should do it:
// first convert all ids to a promise
await Promise.all(myPostsIds.map(id => new Promise(resolve => {
// during this, await every test and update
return Promise.all(allUsers.map(user => new Promise(resolve => {
// if it includes the id, cast the update and then resolve
if (user.bookmarks.includes(id)) {
// if found, resolve the promise for this user after the change
user.updateOne({ $pull: { bookmarks: id } }).then(resolve)
} else {
// resolve directly if not found.
resolve()
}
// when all users are done for this id, resolve the Promise for the given id
}))).then(resolve)
})))
An easier to read and shorter method would be:
for (const id of myPostIds) {
for (const user of allUsers) {
if (user.bookmarks && user.bookmarks.includes(id)) {
await user.updateOne({ $pull: { bookmarks: id } });
}
}
}

Factory tries to create two objects with same properties

I'm writing tests for an express app that implements a user CRUD. Before writing integration tests, I made a factory that would create users for the tests:
factories.js
import faker from 'faker';
import { factory } from 'factory-girl';
import User from '../../src/app/models/User';
factory.define('User', User, {
name: faker.name.firstName(),
email: faker.internet.email(),
password: faker.internet.password(),
admin: false,
});
export default factory;
Nice. Now whenever I needed to create a user for tests, I would just have to use factory.create(), right? Wrong. In one of the test suites I need two users, one that is admin and other that isn't. So I wrote this setup/teardown:
let user, admin;
const createUsers = async () => {
// Runs ok
user = await factory.create('User');
// Error
admin = await factory.create('User', { admin: true });
};
const removeUsers = async () => {
await user.remove();
await admin.remove();
};
beforeAll(async () => {
await createUsers();
});
afterAll(async () => {
await removeUsers();
mongoose.connection.close();
});
user = await factory.create(...) runs fine, but admin = await factory.create(...) raises a MongoError: E11000 duplicate key error collection.
This is because the factory tries to create a user with an email that is already in use (email is unique for the model User). I could ensure that wouldn't happen by passing a specific email to the create method in the same way I passed admin: true, but that wouldn't make much sense since I created the factory to avoid this kind of trouble.
Can you guys point out what am I doing wrong here? I guess it has something to do with the way I defined my factory. Thanks in advance.
EDIT: As suggested, I fixed it by using factory.sequence.
factory.define('User', User, {
name: factory.sequence('User.name', () => faker.name.firstName()),
lastName: factory.sequence('User.lastName', () => faker.name.lastName()),
email: factory.sequence('User.email', () => faker.internet.email()),
password: factory.sequence('User.password', () => faker.internet.password()),
redefinePassword: false,
admin: false,
});
While faker.internet.email() will create a new fake email every time it is called, you only call it once when defining your template object for the factory. Look into the factory.sequence API for a way to make the factory run some code for each object being created https://www.npmjs.com/package/factory-girl#defining-factories
Or simply pass the function faker.internet.email, without the () and I think factory-girl will call that function each time as well, you can also make your define call take a function that returns this object (after calling faker.internet.email()), so many options!

How to test if a document's property changed?

I'm writing tests for an Express CRUD app. The app has an account recovery route that accepts POST requests with an email in the body. It searches for an user with a given email, changes it's password then sends an email with the new password and returns 200.
So I want to write a test that checks if the user's password has indeed changed.
This was my attempt:
First I created a user factory using the factory-girl lib.
// factory.js file
const { factory } = require('factory-girl');
const User = require('../../models/User');
factory.define('User', User, {
email: factory.sequence('User.email', n => `fakeuser${n}#mail.com`),
name: factory.chance('name'),
password: factory.chance('word')
});
module.exports = factory;
Nice. Then I wrote the test:
// One test inside my test suite
it('should return 200 and change user\'s password', async () => {
const user = await factory.create('User');
const oldPassword = user.password;
const response = await request(app)
.post(endpoint)
.send({ email: user.email });
const passwordChanged = oldPassword !== user.password;
expect(response.status).toBe(200);
expect(passwordChanged).toBe(true);
await user.remove();
});
But passwordChanged is always being evaluated to false, since user.password doesn't change at all. I mean, on the database, it changes (the method works correctly), but on my test the user object is not updated after the request.
In other words, what I want to test here is the state of a document in the database, not only what the request is returning. Any suggestions on how to achieve this?
You can open a change stream and listen for when your fields are changed, then run the comparison right then.

Set on firebase and then set firebase claims

So i working with firebase auth and database in order to set new user to data base, if set successful i want to set claims for that user.
So it means i have a promise within a promise:
function setUser(user){
// no need for the database code before this, but userRef is set properly
return userRef.set(user)
.then(succ => {
return firebase.firebase.auth().setCustomUserClaims(user.key, {admin: true})
.then(() => {
console.log("setting claims")
return true;
});
})
.catch(err => {
return err
})
}
calling function:
app.post("/register_user",jsonParser,async (req, res) => {
var user = req.body.user;
let result = await fireBase.setUser(user);
res.send(result);
})
What happens is that i get the set on the database but claims are not set nor i can i see the log. I know its a js question and not firebase one. I tried many different ways (with await) but non worked.
firebase.firebase does not seem correct. You need to be using the admin object which can be initialised using const admin = require('firebase-admin'); This is not part of the firebase db sdk, but the admin one. You can also use the userRef.uid as that gives you the id of the document of the user, if that is what you want, else use your user.key
return admin.auth().setCustomUserClaims(userRef.uid, {
admin: true
}).then(() => {
//on success
});

Jest Issue with mocking api call and checking error

I am using jest and trying to test an asynchronous login request. I am able to check that the call has resolved and is successful. I would also like to test the case that the call wasn't successful.
I have been following the docs from here.
I understand I am not doing the reject correctly, but if I move the jest.mock('.utils/api', () => {... into the test block it doesn't work, it needs to be outside. Can anyone advise the correct way to do this?
See my code below:
import React from 'react';
import { render, fireEvent } from 'react-testing-library';
import Login from './index';
import { login as mockLogin } from './api';
let mockData = {
token: '12345'
};
let errorData = {
message: 'Your username/password is incorrect'
};
jest.mock('.utils/api', () => {
return {
jsonRequest: jest.fn(() => new Promise((resolve, reject) => {
resolve(mockData,);
// I am not doing this correctly.
reject(errorData);
})),
};
});
describe('<Login />', () => {
it('returns a sessionId if successful and error if not', () => {
const { getByLabelText, getByText } = render(<Login />);
const loginButton = getByText(/login/i);
fireEvent.click(loginButton);
expect(mockLogin).toBeCalledTimes(1);
expect(mockLogin).toHaveBeenCalledWith('/login', {
data: {
password: 'test',
username: 'test',
},
method: 'POST',
});
expect(mockLogin()).resolves.toBe(mockData);
expect(mockLogin()).rejects(mockData);
});
});
What you need to test here is the behavour of your component when the API rejects the request for some reason.
Assume this scenario:
Lets say the reason for rejection is that the "Entered password is not correct".
Then you need to make sure that the Login component will show an error message to the DOM where the user can see it and re-enter his password
To test this you need to make a check inside the mocked API, something like:
jsonRequest: jest.fn((formDataSubmittedFromComponent) => new Promise((resolve, reject) => {
// Notice that the mocked function will recieve the same arguments that the real function recieves from the Login component
// so you can check on these arguments here
if (formDataSubmittedFromComponent.password !== mockData.password) {
reject('Entered password is not correct') // << this is an error that your component will get and should handle it
}
resolve();
})),
After that you should test how did your component handle the reject
For example, you could test whether or not it displayed an error message to the DOM:
const errorMessageNode = getByTestId('error-message');
expect(errorMessageNode).toBeTruthy()
Edit: before you fire the login event you should make sure that the form is populated with the mocked data

Categories

Resources