In my app, I do return promise with my mongoose model:
var roomModel = require('../../../models/room').roomModel;
roomModel.findOne({ name: req.body.roomName })
.then(
(room) => {
return new Promise(function(resolve, reject) {
//if no room present, create one, if present, check password
if (room) {
if (room.password === req.body.roomPassword) {
return resolve(room);
} else {
return reject({
code: 401,
message: 'Room password not correct'
});
}
} else {
// create new room with given data
var newRoom = roomModel({});
newRoom.name = req.body.roomName;
newRoom.password = req.body.roomPassword;
//newRoom.users = [];
newRoom.users[0] = {
name: req.body.userName
};
newRoom.save()
.then((data) => {
console.log(data);
if (!data) {
return reject({
code: 500,
message: 'Error when saving room'
});
} else {
return resolve(newRoom);
}
});
}
});
}
)
.then((room) => {
room.findOne({ 'users.name': req.body.userName })
.then((user) => {
console.log(user);
});
})
room.js model:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = require('./user').userSchema;
var room = new Schema({
name: String,
password: String,
users: [userSchema]
});
module.exports.roomSchema = room;
module.exports.roomModel = mongoose.model('room', room);
users.js model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var user = new Schema({
name: String
});
module.exports.userSchema = user;
module.exports.userModel = mongoose.model('user', user);
but when I try to call .findOne() function on this returned model, I get following error:
TypeError: room.findOne is not a function
is model passed in the promise not a model in next .then() statement?
Found problem by myself: I was passing not the model, on which I can use find operations, but document, on which I can perform save options (not find, since it's not a model).
Well, as the docs say "queries are not promises".
There's even a findOne() example in there...
Change your code to
roomModel.findOne({ name: req.body.roomName }).exec().then(/* your stuff */)
and you may have more luck.
You missed out exec() method in your query, try to use and get resolved.
roomModel.find({ name: req.body.roomName }).exec().then(/* your stuff */)
I should think what you're doing is dangerous. Calling a queries then() multiple times might
lead to multiple query calls.
https://mongoosejs.com/docs/queries.html#queries-are-not-promises
Also, there is no need to do exec().then(). Just calling then() executes the query; a better way to use exec() is to actually pass a callback to it.
in my case it happened that no matter how I exported it it wasn't working, and I found this solution:
var {roomModel} = require('../../../models/room')
Put the import within {}
and export it normally, i do like this:
var room = mongoose.model('room',roomSchema);
module.exports.room = room;
Related
I have a problem. I am new to node.js and mongoDB (using mongoose). In MySQL when I have defined a table with required fields the database will refuse to accept input that don't conform to the model's rules. I have noticed that in mongoDB, at least, the way I have set it up, this is not the case.
I have defined the following model in blog-schema.js:
const mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = mongoose.Schema({
title: {
type:String,
required: true,
},
author: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
text: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
},
})
module.exports = mongoose.model('BlogPost', userSchema, 'blog');
In this, I set required:true for all fields apart from date. I then implemented this in conn.js:
const mongoose = require('mongoose')
, BlogPost = require('./schemata/blog-schema')
, db_config = require('./config')
, uri = 'mongodb://' + db_config.user + ":" + db_config.password + "#" + db_config.host + db_config.database;
DataFunctions = function (){
mongoose.connect(uri, db_config.opts);
mongoose.Promise = global.Promise;
this.connections = {};
this.schemas = {};
this.schemas.BlogPost = BlogPost;
this.connections.db = mongoose.connection;
};
DataFunctions.prototype.insert = function(data = {}, callback = null) {
var schema = this.schemas.BlogPost;
this.connections.db.on('error', console.error.bind(console, 'connection error'));
this.connections.db.once('open', function(dataStructure = schema) {
this.items = data;
if (callback != null) {
dataStructure.collection.insertOne(this.items, callback);
mongoose.connection.close();
}
else {
dataStructure.collection.insertOne(this.items, function(err, docs) {
if (err) throw err;
});
mongoose.connection.close();
}
});
mongoose.connection.close();
}
DataFunctions.prototype.retrieve = function(params = {}, columns = '', callback = null) {
var schema = this.schemas.BlogPost;
this.connections.db.on('error', console.error.bind(console, 'connection error'));
this.connections.db.once('open', function(dataStructure = schema) {
if (callback != null) {
dataStructure.find(params, columns, callback);
}
else {
dataStructure.find(params, columns, function(err, data) {
if (err) throw err;
});
}
});
}
module.exports = DataFunctions;
However, when I execute the insert function, it accepts it without error even when fields marked required are left blank. I would really appreciate any assistance in working out how to validate the data inserted into the mongoDB collection.
I am using mongoos version 5.3.6, and mongoDB version 4.0.3
Thank you.
Edit
Thanks to everyone who replied, based on some of the comments below I changed dataStructure.collection.insertOne() to dataStructure.create(), which appears to include validation.
You need to add verification on submit as well or before submit. When the form has errors it is actually invalid so check if it is invalid before submitting..
Tbh you code seems a little verbose, complicated and confusin.. is there are reason you are doing it like this? For example you are using a mongoose schema but not actually submitting with the mongoose methods which is why none of your validations are occuring.. insertOne is not a mongoose method, and you aren't using your model to save the entry. it would be model.save(data)
also you can directly save without having to call the schema again, just declare a new variable.
const post = new BlogPost(data); post.save().then(console.log).catch(console.log);
//also mongoose.connect already returns a promise
mongoose
.connect(
dbUrl,
{ useNewUrlParser: true }
)
.then(() => console.log("Connected"))
.catch(error => console.log("Failed " + error));
I believe you are passing empty string and that's why the validators are not flagging the entries as erroneous. Try passing null for the fields and check the behavaior.
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
I would like to stub the save method available to Mongoose models. Here's a sample model:
/* model.js */
var mongoose = require('mongoose');
var userSchema = mongoose.Schema({
username: {
type: String,
required: true
}
});
var User = mongoose.model('User', userSchema);
module.exports = User;
I have some helper function that will call the save method.
/* utils.js */
var User = require('./model');
module.exports = function(req, res) {
var username = req.body.username;
var user = new User({ username: username });
user.save(function(err) {
if (err) return res.end();
return res.sendStatus(201);
});
};
I would like to check that user.save is called inside my helper function using a unit test.
/* test.js */
var mongoose = require('mongoose');
var createUser = require('./utils');
var userModel = require('./model');
it('should do what...', function(done) {
var req = { username: 'Andrew' };
var res = { sendStatus: sinon.stub() };
var saveStub = sinon.stub(mongoose.Model.prototype, 'save');
saveStub.yields(null);
createUser(req, res);
// because `save` is asynchronous, it has proven necessary to place the
// expectations inside a setTimeout to run in the next turn of the event loop
setTimeout(function() {
expect(saveStub.called).to.equal(true);
expect(res.sendStatus.called).to.equal(true);
done();
}, 0)
});
I discovered var saveStub = sinon.stub(mongoose.Model.prototype, 'save') from here.
All is fine unless I try to add something to my saveStub, e.g. with saveStub.yields(null). If I wanted to simulate an error being passed to the save callback with saveStub.yields('mock error'), I get this error:
TypeError: Attempted to wrap undefined property undefined as function
The stack trace is totally unhelpful.
The research I've done
I attempted to refactor my model to gain access to the underlying user model, as recommended here. That yielded the same error for me. Here was my code for that attempt:
/* in model.js... */
var UserSchema = mongoose.model('User');
User._model = new UserSchema();
/* in test.js... */
var saveStub = sinon.stub(userModel._model, 'save');
I found that this solution didn't work for me at all. Maybe this is because I'm setting up my user model in a different way?
I've also tried Mockery following this guide and this one, but that was way more setup than I thought should be necessary, and made me question the value of spending the time to isolate the db.
My impression is that it all has to do with the mysterious way mongoose implements save. I've read something about it using npm hooks, which makes the save method a slippery thing to stub.
I've also heard of mockgoose, though I haven't attempted that solution yet. Anyone had success with that strategy? [EDIT: turns out mockgoose provides an in-memory database for ease of setup/teardown, but it does not solve the issue of stubbing.]
Any insight on how to resolve this issue would be very appreciated.
Here's the final configuration I developed, which uses a combination of sinon and mockery:
// Dependencies
var expect = require('chai').expect;
var sinon = require('sinon');
var mockery = require('mockery');
var reloadStub = require('../../../spec/utils/reloadStub');
describe('UNIT: userController.js', function() {
var reportErrorStub;
var controller;
var userModel;
before(function() {
// mock the error reporter
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
useCleanCache: true
});
// load controller and model
controller = require('./userController');
userModel = require('./userModel');
});
after(function() {
// disable mock after tests complete
mockery.disable();
});
describe('#createUser', function() {
var req;
var res;
var status;
var end;
var json;
// Stub `#save` for all these tests
before(function() {
sinon.stub(userModel.prototype, 'save');
});
// Stub out req and res
beforeEach(function() {
req = {
body: {
username: 'Andrew',
userID: 1
}
};
status = sinon.stub();
end = sinon.stub();
json = sinon.stub();
res = { status: status.returns({ end: end, json: json }) };
});
// Reset call count after each test
afterEach(function() {
userModel.prototype.save.reset();
});
// Restore after all tests finish
after(function() {
userModel.prototype.save.restore();
});
it('should call `User.save`', function(done) {
controller.createUser(req, res);
/**
* Since Mongoose's `new` is asynchronous, run our expectations on the
* next cycle of the event loop.
*/
setTimeout(function() {
expect(userModel.prototype.save.callCount).to.equal(1);
done();
}, 0);
});
}
}
Have you tried:
sinon.stub(userModel.prototype, 'save')
Also, where is the helper function getting called in the test? It looks like you define the function as the utils module, but call it as a method of a controller object. I'm assuming this has nothing to do with that error message, but it did make it harder to figure out when and where the stub was getting called.
I largely believe this error is due to the object I'm calling not containing the .populate function, although I have no idea how to change this to work.
To start with, here is the error in full.
TypeError: exam[0].modules[u].topics[i].populate(...).exec is not a function
at /home/ubuntu/workspace/tests/app.js:425:84
at Query.Model.$wrapCallback (/home/ubuntu/workspace/tests/node_modules/mongoose/lib/model.js:3336:16)
at /home/ubuntu/workspace/tests/node_modules/mongoose/node_modules/kareem/index.js:259:21
at /home/ubuntu/workspace/tests/node_modules/mongoose/node_modules/kareem/index.js:127:16
at nextTickCallbackWith0Args (node.js:420:9)
at process._tickCallback (node.js:349:13)
Process exited with code: 1
The specific line I'm referring to is exam[0].modules[u].topics[i].populate("questions").exec(function(err,quests) another line I believe is significantly important here is the line examBoard.find({name:req.body.examBoardName},function(err,exam) which returns exam which does not contain the .populate function.
I presume this is largely down to my lack of experience, and not a logical error, but I'm not sure.
Here is the section of code which contains the error.
app.post("/test",function(req,res)
{
console.log("\n\n\n\n")
var time = req.body.time;
var topicName = [req.body.topic1,req.body.topic2,req.body.topic3,req.body.topic4,req.body.topic5];
var topicsArray = [];
examBoard.find({name:req.body.examBoardName},function(err,exam)
{
if(err)
{
console.log(err);
}
else
{
for(var u=0;u<exam[0].modules.length;u++)
{
console.log("exam[0].modules[u]:\n"+exam[0].modules[u]);
console.log("req.body.moduleName:\n"+req.body.moduleName);
if(exam[0].modules[u].name==req.body.moduleName)
{
console.log("topicName[]:\n"+topicName[0]+"\n"+topicName[1]+"\n"+topicName[2]+"\n"+topicName[3]+"\n"+topicName[4]);
for(var i=0;i<exam[0].modules[u].topics.length;i++)
{
console.log("exam[0].modules[u].topics[i].name:\n"+exam[0].modules[u].topics[i].name);
for(var t=0;t<topicName.length;t++)
{
if(exam[0].modules[u].topics[i].name==topicName[t])
{
// exam[0].modules[u].topics[i].find({name:topicName[t]}).populate("questions").exec(function(err,quests)
exam[0].modules[u].topics[i].populate("questions").exec(function(err,quests)
{
if(err)
{
console.log(err);
}
else
{
console.log("exam[0].modules[u].topics[i].questions:\n"+exam[0].modules[u].topics[i].questions);
topicsArray.push({
name:topicName[i],
questions:quests
});
}
});
}
}
}
break;
}
}
}
});
});
Here is the examBoard schema.
var mongoose = require("mongoose");
var topicSchema = new mongoose.Schema({
name: String,
questions:[
{
type:mongoose.Schema.Types.ObjectId,
ref:"question"
}
],
});
var moduleSchema = new mongoose.Schema({
name: String,
topics: [topicSchema]
});
var examBoardSchema = new mongoose.Schema({
name: String,
modules: [moduleSchema]
});
module.exports = mongoose.model("examBoard", examBoardSchema);
And here just in case there may be something wrong here, is the importing of the schema.
var express = require("express"),
mongoose = require("mongoose"),
passport = require("passport"),
bodyParser = require("body-parser"),
LocalStrategy = require("passport-local"),
passportLocalMongoose = require("passport-local-mongoose"),
seedDB = require("./seeds"),
question = require("./models/question"),
examBoard = require("./models/examBoard"),
user = require("./models/user");
You are invoking populate method from exam[0].modules[u].topics[i] but actually the model object that hold this method is exam[0] so you can populate questions in your exam in a deep object-hierarchy like this:
exam[0].populate("modules.topics.questions")
But, wait a sec, now the model will populate the questions in all topics in all modules within this exam.
In your case, you filter by moduleName first, so you can configure populate options, so to be like this:
var options = {
path: 'modules.topics.questions',
match: { 'modules.name': req.body.moduleName }
};
exam[0].populate(options)
Lear more about populate parameters from docs.
I tried to use save() in Mongoose but saw empty collection been inserted.
my photo schema is like this :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var photosSchema = mongoose.Schema({
photos: {
type: String
},
caption: {
type: String
}
});
var Photos = module.exports = mongoose.model('photos', photosSchema);
module.exports.getAllPhotos = function(callback){
Photos.findAll(callback);
}
and in my route I do
var Photo = require('../models/photos');
router.post('/upload_photo', upload.any(), function(req, res, next) {
var photo = new Photo();
var data = {
photos:'abc.jpg',
caption:'something..'
}
photo.save(data);
res.end();
});
Am I using save wrong here? I know I can define a method like savePhoto and export it, but how to use save() directly instead?
Try it like so, i.e. pass the data directly to the constructor and end the response stream when the async. save finished (and maybe add error handling as well).
var photo = new Photo({
photos:'abc.jpg',
caption:'something..'
});
photo.save(function(err) {
res.end();
});
There is also a shorthand create method on the model as described in the Mongoose docs
Try like this, and you will be able to see the issue if there's any:
var data = {
photos:'abc.jpg',
caption:'something..'
};
var photo = new Photo(data);
photo.save(function (err) {
if (err) console.log(err);
res.end();
});