How do you mock MySQL (with node-orm2) in Node.js/Express? - mysql

I am using node.js/express with https://github.com/dresende/node-orm2 to use my MySQL database.
I am new to the node.js world and I am quite stuck so far, I don't know how to unit test (not integration test) a simple function.
Here is my server.js, loading my user model (ORM)
var express = require('express'),
orm = require('orm'),
config = require('./config/config.js'),
auth = require('./services/authentication'),
helper = require('./middlewares/helper.js'),
friends = require('./modules/friends.js');
var app = express();
app.use(orm.express('mysql://' + config.mysql.username + ':' + config.mysql.pwd + '#' + config.mysql.host + ':' + config.mysql.port + '/' + config.mysql.db, {
define: function(db, models, next) {
db.load("./models/models.js", function(err) { // loaded!
models.user = db.models.user;
});
}
}));
var middlewares = [auth.authenticate, helper.retrieveUser];
app.get('/friends', middlewares, friends.findActiveFriends);
app.listen(3000);
console.log('Listening on port 3000...');
here is the user model :
module.exports = function (db, cb) {
var User = db.define('user', {
uid : { type: 'number', rational: false, unique: true, required: true },
first_name : { type: 'text', size: 100, required: true },
last_name : { type: 'text', size: 100, required: true },
picture : { type: 'text', size: 255, required: false },
email : { type: 'text', size: 255, required: true },
creation_date : { type: 'date', time: true },
modification_date : { type: 'date', time: true }
}, {
methods: {
fullName: function () {
return this.first_name + ' ' + this.last_name;
}
},
hooks: {
beforeCreate: function (next) {
if (this.creation_date == undefined) {
this.creation_date = new Date();
}
if (this.modification_date == undefined) {
this.modification_date = new Date();
}
return next();
}
}
});
// CUSTOM FUNCTIONS
User.getByUid = function(uid, callback) {
this.find({ uid: uid }, function(err, users) {
if(err) callback(err);
if (users.length == 1) {
callback(null, users[0]);
} else {
callback('No user found with uid=' + uid);
}
});
};
User.hasMany("friends", User, {
status: { type: 'enum', values: ['pending', 'refused', 'active'] }
}, {
reverse: 'friendsrev', mergeId: 'user_id', mergeAssocId: 'friend_id'
});
return cb();
};
and here is my methods to find active friends in friends.js:
var _findActiveFriends = function(req, res) {
req.currentUser.getFriends({
status: 'active'
}, function(err, friends) {
if (err) throw err;
res.send(JSON.stringify(friends));
});
};
I would like to know how can I write a simple test (with mocha and sinon.js ?) by mocking the database connection and the request also. I need to mock the value of req.currentUser which is a user returned by the ORM in a middleware.
I just want to run unit tests and do not use a real DB or make some HTTP calls.
thanks for your help.

If you want to mock the req using sinon.js, you can do something like the following.
var sinon = require('sinon');
var friend = require('./friend');
it('some test', function(done) {
var req = {
currentUser: {
// Add all the properties/functions that you are concerned with here.
// and you can/should wrap them around sinon.spy().
// If you are not concerned with that function (i.e. you are not using it)
// then you can simply use sinon.stub() to return a stub function.
}
};
var res = {
send: sinon.spy(function(obj) {
assert.ok(obj); // yes object exists
done(); // end of test
};
};
var next = function() {};
friend.findActiveFriend(req, res, next);
});
This way you shouldn't be connecting to the model, which tests friend.js only.
Also, since I just noticed you are using orm.express, you may also want to simply mock req.models with the stubbed function you desire as above.

Related

Sequelize showing error: Unexpected token u in JSON at position 0

I am trying to update a record in mysql database using sequelize but it is not working.
I am getting this error
Unexpected token u in JSON at position 0
Model
module.exports = sequelize.define("branches", {
address: Sequelize.TEXT(),
company: Sequelize.STRING(),
codeConfig: {
type: Sequelize.STRING,
allowNull: false,
get: function () {
return JSON.parse(this.getDataValue('codeConfig'));
},
set: function (val) {
return this.setDataValue('codeConfig', JSON.stringify(val));
}
},
});
Update function
router.put('/:id', async (req, res) => {
const { address, company} = req.body;
try {
const branches = await Branches.findOne({ where: { code: req.params.id } });
if (!branches) return res.json({ msg: "Branch Not Found" });
Branches.update({ "address": "No. 10 distreet street" }, {
where: {
code: "WHJ5uBdriI"
}
}).then(function (newBranch) {
return res.json({ msg: "Updated" });
});
} catch (error) {
console.error(error.message);
res.status(500).send("Server Error");
}
});
Error output
Add autoJsonMap: false, to your sequelize's dialectOptions
Example:
let sequelize = new Sequelize(DATABASE, USER, PASSWORD, {
// some other options
dialectOptions: {
autoJsonMap: false,
}
});
Reference:
https://github.com/sequelize/sequelize/issues/12583
i have noticed that before sequelize make a field update, it fetches through all fields, and then execute a getter function if exist, so for that i added an if check inside a getter, here is the code now the model.update working:
get: function () {
if(this.getDataValue('codeConfig') !== undefined){
/// appentely sequelize tried to parse the value of 'codeConfig' but its undefined since you are updating only address field.
return JSON.parse(this.getDataValue('codeConfig'));
}
},

Router is not recieving JSON from model?

I'm trying to pass an object from a model to my route so I can finish my login system but I'm not recieving anything.
Model code:
const AWS = require('aws-sdk');
const bcrypt = require('bcryptjs');
const config = require('../config/config.json');
var dynamoose = require('dynamoose');
const express = require('express');
var Schema = dynamoose.Schema;
const USER_SCHEMA = new Schema({
username: {
type: String,
required: true
},
firstName: {
type: String
},
lastName: {
type: String
},
email: {
type: String,
required: true
},
credential: {
type: String
},
password: {
type: String,
required: true
}
})
const USER = module.exports = dynamoose.model('Usuarios', USER_SCHEMA);
module.exports.getUserByUsername = function (user, callback) {
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "Users",
KeyConditionExpression: "#us = :uuuu",
ExpressionAttributeNames: {
"#us": "username"
},
ExpressionAttributeValues: {
":uuuu": user
}
};
docClient.query(params, function (err, data) {
if (err) {
console.error("Unable to query. Error:", JSON.stringify(err, null, 2));
} else {
data.Items.forEach(function (user, callback) {
console.log(user.username + ": " + user.password + user.email + user.firstName);
});
}
callback(null, user);
});
}
This is working fine, I can print user.username, user.password and so on, but for some reason my router is not importing the JSON
router.post('/authenticate', (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
USER.getUserByUsername(username, (err, user) => {
if (err) throw err;
if (!user) {
return res.json({
success: false,
"msg": "User not found"
});
}
USER.comparePassword(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
const token = jwt.sign({
username: user
}, secret.secret, {
expiresIn: 86400
});
res.json({
success: true,
token: 'JWT ' + token,
user: {
user: user.username,
password: USER.password,
email: user.email
}
});
} else {
return res.json({
success: false,
msg: 'Wrong password'
})
}
});
});
});
The res.json from comparePassword should be the object from the route (which is the user model) but is not recieving a thing. I have tried with USER.username/email/etc user.username/email/etc but nothing works.
I know I must be missing something somewhere, but where?
Edit: Also tried with module.export.user = user; inside the model

Updating sub array in JSON with a REST API in Mean Stack

I'm developing a MEAN stack application and I'm hung up on how to actually update a document that has been saved into the MongoDB already. I've seen that I have to use patch instead of post in my REST API paths, but it's still a little clouded to me. I want to insert a new Package into the Package JSON Array in the User JSON.
Possible Duplicate, but he's overriding a value in the array and not adding a new object into it.
My JSON Schema:
//User schema
const UserSchema = mongoose.Schema({
name: {
type: String
},
email: {
type: String,
require: true
},
username:{
type:String,
required: true
},
password:{
type:String,
required: true
},
packages: [{
from: String,
to: String,
tracking: String
}]
});
My REST API Paths
//Update
router.patch('/update', (req, res) => {
const username = req.body.username;
const packages = req.body.packages;
User.getUserByUsername(username, (err, user) => {
if(!user){
return res.json({success: false, msg: 'User not found'});
} else {
User.addPackages(user, req.body.packages, (err, user) => {
if(err){
res.json({success: false, msg:'Failed to update packages'});
} else {
res.json({success: true, msg:'update packages'});
}
})
}
});
});
My Module's:
module.exports.addPackages = function(user, packages, callback){
User.findOneAndUpdate(
{username:user.username},
{$push: {"packages" : {
"to" : packages.to,
"from" : packages.from,
"tracking" : packages.tracking
}}},
{new:true},
function(err, newPackage){
if (err) throw err;
});
}
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
module.exports.getUserByUsername = function(username, callback){
const query = {username: username}
User.findOne(query, callback);
}
They're updating into my MongoDB, but just the object ID and not the values...
db.your_collection.update({},
{$set : {"new_field":1}},
{upsert:false,
multi:true})

Sequelize findAll is not a function

I'm making a project with Sequelize and I'm stucked in this step. The problem is that when I try to log in and the passport-local code is executed, when it reaches the User.findAll(...) it throws that findAll is not a function.
If I make console.log(User) it shows [function].
My structure:
/config/config.js
/config/passport.js
/models/index.js
/models/nuke_users.js (generated by sequelize-auto)
/index.js
config.js:
//Setting up the config
var Sequelize = require('sequelize');
var sequelize = new Sequelize('rocarenav2', 'root', '123456', {
host: "localhost",
port: 3306,
dialect: 'mysql'
});
module.exports = sequelize;
passport.js:
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var User = require('../models/nuke_users');
var crypto = require('crypto');
function hashPasswordForNuke(password) {
return md5password = crypto.createHash('md5').update(password).digest('hex');
}
// expose this function to our app using module.exports
module.exports = function(passport) {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, {})
.then(function (user) {
done(err, user);
})
.catch(function (error){
done(error);
});
});
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
User.findAll({
where: {
'user_email': email
}
}).then(function (user) {
if(!user)
return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
if ((user.user_password).localeCompare(hashPasswordForNuke(password)) === -1)
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
return done(null, user);
})
.catch(function (error){
done(error);
});
}));
};
models/index.js
'use strict';
var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var basename = path.basename(module.filename);
var config = require(__dirname + '/../config/config');
var db = {};
//Create a Sequelize connection to the database using the URL in config/config.js
var sequelize = config;
//Load all the models
fs
.readdirSync(__dirname)
.filter(function(file) {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(function(file) {
var model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function(modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
//Export the db Object
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
/models/nuke_users.js
/* jshint indent: 2 */
module.exports = function(sequelize, DataTypes) {
return sequelize.define('nuke_users', {
user_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "",
references: {
model: 'reps_table',
key: 'PostName'
}
},
user_email: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: ""
},
user_avatar: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: ""
},
user_password: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'nuke_users'
});
};
/index.js
...
var models = require('./models/');
...
So, what am I doing wrong?
The nuke_users module is exporting a function that, when called, returns the Model. Because you aren't calling this function, it is not returning the Model, and thus the function you are looking for does not exist.
To call this exported function you would need to pass in the sequelize instance and DataTypes, as so:
var User = require('../models/nuke_users')(sequelize, DataTypes);
In your case you are using a loader in the index.js file, and it is exporting the db object which contains the models keyed by their name.
var models = require('../models'); // loads index.js
var User = models.nuke_user; // the model keyed by its name
User.findOne(...); // search the model
Instead of returning the model, export it from NukeUser.js:
const NukeUser = sequelize.define('nuke_users', {
// ...
});
module.exports = NukeUser;
Then in index.js:
const NukeUser = require('../models/NukeUser');
NukeUser.findAll() //.then() ...
You need to check the models route in the collections, for example, in my case, I had read() as a method in my collections like that:
async read(id) {
try {
if(id) {
return await this.model.findOne({where: {id: id}});
} else {
return await this.model.findAll();
}
} catch (e) {
console.error(`Error in reading data with the id: ${id}`);
}
}
So, findAll() wouldn't work. Instead, I used read()

Sailjs-Waterline: Serialize, Parse and initialize an Model

Let's assume that I found user using next command:
User.findOne(id).exec(function(err, user){
redis.set(JSON.stringify(user), id);
})
After that I'm loading from redis my object
redis.get(id, function(err, reply){
if(!err && reply) {
var user = JSON.parse(reply);
// here methods like user.save() or any of defined manually by developer is unavailable
//
} else {
..
}
})
User model example:
module.exports = {
attributes : {
// Simple attribute:
// name: 'STRING',
// Or for more flexibility:
// phoneNumber: {
// type: 'STRING',
// defaultValue: '555-555-5555'
// }
email : {
type: 'string',
unique: true
},
// ...
verifyPass: function(pass, callback) {
var obj = this.toObject();
if (callback) {
return Password.compare(pass, obj.local.password, callback);
}
return Password.compareSync(pass, obj.local.password);
},
// retrieve profile for sending to front end
getProfile: function() {
var obj = this.toObject();
var profile = {};
profile.id = obj.id;
// ...
return profile;
},
I need all of that methods to be work whenever I parse waterline model from json. Is there a way to initialize it without triggering db at all. Also would be nice if I could to call user.save().
There's currently no documented public API for this unfortunately, but you can use,
var user = new PersonCollection._model(values, {showJoins: true});
See how that works for you!