How to test if a document's property changed? - javascript

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.

Related

Show user invoices for simultaneously logged in users using Expressjs

I have created a simple invoice application using the MERN stack. The application is great at handling data for the logged in user as long as one user is logged in, but if another user logs in then the invoices for the user that first logged in is shown.
I am currently using app.set() and app.get() to pass data between endpoints and send to my frontend client. Auth0 handles the authentication layer, but would express-session solve this issue? And if it is how would I go about implementing this? Or is there a better solution?
Below is the code that sends the invoices currently to the frontend:
var express = require('express');
var app = express();
var userInvoices = express.Router();
const axios = require('axios');
const InvoiceModel = require('../models/Invoice');
const UserModel = require('../models/User');
//Functions//
async function clientCall() {
const url = `${process.env.REACT_APP_SAVE_USER}`;
const axiosConfig = {
method: 'get',
url
};
await axios(axiosConfig).catch((e) => {console.log(e)});
};
async function fetchUsersFromDB() {
const usersFromDB = await UserModel.find().catch((e) => {console.log(e)});
return usersFromDB;
};
async function saveUser(User) {
const condition = {email: User.email};
const query = {
nickname: User.nickname,
name: User.name,
picture: User.picture,
email: User.email,
email_verified: User.email_verified,
sub: User.sub,
};
const options = { upsert: true };
const update = await UserModel.updateMany(condition, query, options).catch((e) => {console.log(e)});
// Log the amount of documents that where updated in the DB.
if(update.nModified > 0 ) {
console.log('Number of Users added or updated to DB:', update.nModified)
}
};
function findCommonUser(dbUser, User) {
if(dbUser.length <= 0) {
UserModel.create(User, () => {console.log('Users saved to database')});
console.log('An Error has occured with Database')
} else {
dbUser.forEach((DBElement) => {
if(User.email !== DBElement.email) {
saveUser(User);
}
})
}
console.log(' Users match')
};
function matchUserAndInvoice(dbInvoices, loggedInUser) {
let newArray = [];
dbInvoices.forEach(async (DBElement) => {
if(DBElement.customer_email === loggedInUser.email){
newArray.push(DBElement);
app.set('userInvoices', newArray);
}
})
}
// prevents user from having to refresh to get data.
clientCall();
userInvoices.post('/saveUser', async (req, res) => {
try {
const User = req.body;
const usersFromDB = await fetchUsersFromDB().catch((e) => {console.log(e)});
findCommonUser(usersFromDB, User);
app.set('Users', User)
} catch (error) {
console.log(error)
}
})
userInvoices.get('/fetchUserInvoices', async (req,res) => {
try {
const invoices = await InvoiceModel.find().catch((e) => {console.log(e)});
const user = await app.get('Users');
await matchUserAndInvoice(invoices,user);
const userInvoices = await app.get('userInvoices')
res.json(userInvoices);
} catch (error) {
console.log(error)
}
});
;
module.exports = userInvoices;
app.get() is essentially global to your server instance so putting data there to use between requests for individual users will (as you have discovered) get data confused between different users as all users are trying to store data in the same place.
The usual way to solve a problem like this is to use express-session. This cookies the end-user's connection the first time they connect to your server and then creates a server-side object that is automatically associated with that cookie. Then, inside of any request handler, you can read or set data in req.session and that data will uniquely belong to just that user.
If the user changes devices or clears their cookies, then the association with the session object for that user will be lost (creating a new session object for them upon their next connection). But, if you have a persistent data store and you use some sort of login that allows you to populate the session from the user's persistent store, you can even make the session persistent across devices for the same user (though often times this is not required).
In the specific application you describe in your question (tracking invoices), it seems like your invoice data should be tagged with a specific user when it is stored in your database and then any future requests to display invoices should query based on the particular user that is making the request. This requires a login and login cookie so that you can uniquely identify each user and thus show them only the invoices that pertain to them.
The above-described session object should only be used for temporal session state, not for persistent storage such as invoices. If your server implementation is truly RESTFUL, you may not even need any data stored in the session object other than user's logged in userID.

TypeError: Cannot set property 'user' of undefined<br> at [file path] sqlite3

I am receiving an undefined error when attempting to set a session for the user upon validation of credentials on login. I am trying to use express-session to create the session for the user but do not have it directly imported into the file (I was guided to not do so) but am unsure how to resolve this error given. Any help and insight would be much appreciated!
End point:
router.post("/login", async (req, res, next) => {
try {
const { username, password } = req.body
// * checks for record existence in db, assigns record to var for access
const user = await users_access.findByFilter({ username })
if (!user) {
return res.status(401).json({ message: 'invalid crededentials' });
}
// * compares the entered password to the hash on record
const passwordValid = await secure.compare(password, user.password)
// * handling invalid responses + creating session
if (!passwordValid) {
return res.status(401).json({ message: 'invalid credentials' });
}
req.session.user = user
res.json({ message: `welcome, ${user.username}!`})
} catch(error) {
next(error)
}
});
application model:
// * add users to the datbase
// * inserts argument into user table in db access
// * returns a user found by id
const add = async (user) => {
const [id] = await database_access("users")
.insert(user)
return findById(id)
}
// * find user record with username and password
const find = () => {
return database_access("users").select("id", "username")
}
// * find user by filter
const findByFilter = (filter) => {
return database_access("users")
.select("id", "username", "password")
.where(filter)
.first()
}
// * find user with id
const findById = (id) => {
return database_access("users")
.select("id", "username")
.where({ id }) // desctructuring id from the request
.first()
}
module.exports = {
add,
find,
findByFilter,
findById
}
if you need to see any additional code to assess I am happy to provide but believe this is the source of issue per the error response. Thank you in advanced!
so I guess you are using the express-session module in your entry file for the server, app.js, server.js, index.js however you call it.
this login handler require to be used in a context where the session is available.
I think what you want is not a unit test for this particular router, but an integration test, to test this router in the context of your app.
This is all I can see from the information you provided. If this was not helpful enough, maybe you can show us your servers main file. and how this router is used.

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 Testcafe handle login errors?

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());
});

How to get data passed to mongoose schema constructor

I am testing my application and need to verify that mongoose schema constructor is called with correct data.
let's say I do this:
const UserData = new User(user)
console.log(UserData.contructor.args)
I would expect log of the user object.
Probably the data is passed to constructor of mongoose schema?
Can some one please advise me how to access it?
Here is specific case I am trying to solve.
export const signup = async (req, res, next) => {
try {
//if user object is missing return error
if (!req.body.user)
return next(boom.unauthorized('No user data received.'))
//get user data
const user = req.body.user,
{ auth: { local: { password, password_2 } } } = user
//check if both passwords match
if (password !== password_2)
return next(boom.unauthorized('Passwords do not match.'))
//check if password is valid
if (!Password.validate(password)) {
const errorData = Password.validate(password, { list: true })
return next(boom.notAcceptable('Invalid password.', errorData))
}
//creates new mongo user
const UserData = new User(user)
//sets user password hash
UserData.setPassword(password)
//saves user to database
await UserData.save()
//returns new users authorization data
return res.json({ user: UserData.toAuthJSON() })
} catch(err) {
//if mongo validation error return callback with error
if(err.name === 'ValidationError') {
return next(boom.unauthorized(err.message))
}
// all other server errors
return next(boom.badImplementation('Something went wrong', err))
}
}
And part of test:
describe('Success', () => {
it('Should create new instance of User with request data', async () => {
const req = { body },
res = {},
local = { password: '1aaaBB', password_2: '1aaaBB'},
constructorStub = sandbox.stub(User.prototype, 'constructor')
req.body.user.auth.local = {...local}
await signup(req, res, next)
expect(constructorStub.calledOnceWith({...req.body.user})).to.be.true
})
})
EDIT: I can verify that is is called with expect(constructorStub.calledOnce).to.be.true
Just can't get to verify data passed.
Edit: After talking for some time sounds like what you need is to validate that you are creating a new user correctly.
My suggestion here is to create a new function createUserFromRequest that would take in request and return a new User.
You can then test this function easily as it's pure (no side effects, just input and output).
At this point, most of the logic in your handler is in this function so it would be probably not worth testing the handler itself, but you could still do it, for example by mocking the function above.
Example:
function createUserFromRequest(request) {
//get user data
const user = req.body.user,
{ auth: { local: { password, password_2 } } } = user
//check if both passwords match
if (password !== password_2)
return next(boom.unauthorized('Passwords do not match.'))
//check if password is valid
if (!Password.validate(password)) {
const errorData = Password.validate(password, { list: true })
return next(boom.notAcceptable('Invalid password.', errorData))
}
//creates new mongo user
const UserData = new User(user)
//sets user password hash
UserData.setPassword(password)
return UserData;
}
Please note: stubs and mocking are usually a code smell: there could either be a better way of testing, or it could be a sign of a need to refactor the code into something more easily testable. They usually point to tightly coupled or cluttered code.
Check out this great article on that topic: https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a

Categories

Resources