Mongoose "disconnect is not a function" - javascript

I'm using this tutorial to make a node/mongo application. When I run addContact, it seems like the contact saves to the DB but, I get an error saying that the disconnect function is not a function. Can anyone tell me why this is happening and, how to fix it? I'm assume that there's some issue with the scope of the constant, db?
code:
const mongoose = require('mongoose');
const assert = require('assert');
mongoose.Promise = global.Promise;
const db = mongoose.connect('mongodb://localhost:27017/contact-manager');
function toLower(v) {
return v.toLowerCase();
}
const contactSchema = mongoose.Schema({
firstname: { type: String, set: toLower },
lastname: { type: String, set: toLower },
phone: { type: String, set: toLower },
email: { type: String, set: toLower }
});
const Contact = mongoose.model('Contact', contactSchema);
const addContact = (contact) => {
Contact.create(contact, (err) => {
assert.equal(null, err);
console.info('New contact added');
db.disconnect();
});
};
const getContact = (name) => {
const search = new RegExp(name, 'i');
Contact.find({$or: [{firstname: search }, {lastname: search }]})
.exec((err, contact) => {
assert.equal(null, err);
console.info(contact);
console.info(`${contact.length} matches`);
db.disconnect();
});
};
module.exports = { addContact, getContact };
code part2:
const program = require('commander');
const {addContact, getContact} = require('./logic');
program
.version('0.0.1')
.description('Contact management system');
program
.command('addContact <firstame> <lastname> <phone> <email>')
.alias('a')
.description('Add a contact')
.action((firstname, lastname, phone, email) => {
addContact({firstname, lastname, phone, email});
});
program
.command('getContact <name>')
.alias('r')
.description('Get contact')
.action(name => getContact(name));
program.parse(process.argv);
error:
New contact added
/Users/user/contact-manager/node_modules/mongodb/lib/utils.js:132
throw err;
^
TypeError: db.disconnect is not a function
at Contact.create (/Users/user/contact-manager/logic.js:33:8)
at Function.<anonymous> (/Users/user/contact-manager/node_modules/mongoose/lib/model.js:3913:16)
at parallel (/Users/user/contact-manager/node_modules/mongoose/lib/model.js:2077:12)
at /Users/user/contact-manager/node_modules/async/internal/parallel.js:35:9
at /Users/user/contact-manager/node_modules/async/internal/once.js:12:16
at iteratorCallback (/Users/user/contact-manager/node_modules/async/eachOf.js:52:13)
at /Users/user/contact-manager/node_modules/async/internal/onlyOnce.js:12:16
at /Users/user/contact-manager/node_modules/async/internal/parallel.js:32:13
at apply (/Users/user/contact-manager/node_modules/lodash/_apply.js:15:25)
at /Users/user/contact-manager/node_modules/lodash/_overRest.js:32:12
at callbackWrapper (/Users/user/contact-manager/node_modules/mongoose/lib/model.js:2046:11)
at /Users/user/contact-manager/node_modules/mongoose/lib/model.js:3913:16
at model.$__save.error (/Users/user/contact-manager/node_modules/mongoose/lib/model.js:342:7)
at /Users/user/contact-manager/node_modules/kareem/index.js:297:21
at next (/Users/user/contact-manager/node_modules/kareem/index.js:209:27)
at Kareem.execPost (/Users/user/contact-manager/node_modules/kareem/index.js:217:3)

mongoose.connect does not return a value, as far as I can tell from looking at the code given in the getting started guide. If you want to close the database connection, you need to call the close method on mongoose.connection which is written to when you open a connection with mongoose.connect. The simplest way to do this is to move your mongoose.connect to its own line:
mongoose.connect('mongodb://localhost:27017/contact-manager');
const db = mongoose.connection;
Then just replace your calls to db.disconnect() with calls to db.close().

Try letting mongoose handle the connection closure using mongoose.connection.close()

Related

Proxyquire not calling inner functions (npm modules) and does not work with classes properly

Where am i going wrong here?
Using mocha, chai, sinon and proxyquire for an express server and sequelize ORM linked with a postgres database
I am trying to test a login controller route from my express server
Before I show the file which I want to run my test on here is what "../services/authService.js" file looks like
../services/authService
const UserService = require("./userService");
module.exports = class AuthService extends UserService {
};
// so UserService will have the method findByEmail
// UserService class looks like this and it is coming from another file require("./userService.js) as stated above
/*
class UserService {
async findByEmail(email) {
try {
const user = await User.findOne({ where: { email: email }});
if (user) {
return user;
}
throw new Error("User not found");
} catch (err) {
err.code = 404;
throw err
}
}
}
*/
And here is the auth-controller.js file which I want to run the test on
auth-controller.js
const bcrypt = require('bcryptjs');
const AuthService = require("../services/authService"); // is a class which extends from another calls see the code above
const authService = new AuthService();
const jwtGenerator = require('../utils/jwtGenerator');
const createError = require("http-errors");
exports.loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) throw createError(401, 'Incorrect password.');
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully 😊 👌", user, token });
} catch (error) {
next(error);
}
}
This code works in production but I cannot seem to test it. I used proxyquire to require the modules that the function uses. I have a big problem in making proxyquire work when it comes to my class AuthService here is my test file. As proxyquire is not working with classes some how. proxyquire is not using make AuthServiceMock at all cant figure out why.
First of these are my helper variables which I will use in the test file
../test-utils/user-helper
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let loginAdminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const loginUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const adminUser = {
id: 1,
email: 'admin#test.com',
password: '123456',
is_admin: true,
first_name: 'john',
last_name: 'doe',
created_at: "2020-06-26T09:31:36.630Z",
updated_at: "2020-06-26T09:31:49.627Z"
}
module.exports = {
createAccessToken,
loginDetail,
loginAdminUser,
loginUser,
adminUser
}
And here is the test file I placed comments espcially around proxyquire when I am trying to use it as this is giving me some issues when it comes to using it with classes. And as well it is not calling mocked/stubbed npm modules for some reason
auth-controller.spec.js
"use strict";
const _ = require("lodash");
const path = require("path");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const chai = require("chai");
const { expect } = chai;
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
// const AuthServiceOriginalClass = require("../../services/authService"); If i use this directly in proxyquire it calls the original class
const { createAccessToken, loginDetail, loginAdminUser, loginUser, adminUser } = require("../test-utils/user-helper");
const controllerPath = path.resolve('./controllers/authController.js');
describe("login route", () => {
let proxy, authService, bcryptStub, fakeCallback, fakeReq, fakeRes, fakeNext, resolveFn, token;
let result, bcryptStubbing, response;
class UserServiceMock {
async findByEmail(email) {
try {
if (email) {
return loginAdminUser;
}
} catch (error) {
throw error;
}
}
}
class AuthServiceMock extends UserServiceMock {};
bcryptStub = {
compare: function() { return true }
};
let tokeen = (kk) => {
return createAccessToken(kk);
}
// token = sinon.mock(createAccessToken(loginAdminUser)); // ?? which 1 to use?
token = sinon.spy(createAccessToken); // ?? which 1 to use?
// token = sinon.stub(createAccessToken) ?? which 1 to use?
proxy = proxyquire(controllerPath, {
"../services/authService.js": AuthServiceMock, // seems like this is not called at all
// "../services/authService.js": AuthServiceOriginalClass, // commented out if use this instead it calls the original class instant
"bcryptjs": bcryptStub,
"../utils/jwtGenerator": token,
// "#noCallThru": true // keep on or off?
});
before("Stub my methods", () => {
authService = new AuthServiceMock();
// If I call the entire loginRoute I want this stub authTry to be called inside of it and resolve that object value
authTry = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(loginAdminUser);
sinon.stub(bcryptStub, "compare").resolves(true); // force it to return true as that seems to be like the code of authController.js
// sinon.stub(token, "createAccessToken")
});
before("call the function loginRoute", async () => {
// fakeCallback = new Promise((res, rej) => {
// resolveFn = res
// });
fakeReq = {
body: {
email: loginDetail.email,
password: loginDetail.password
}
};
fakeRes = {
cookie: sinon.spy(),
status: sinon.spy(),
json: sinon.spy()
}
fakeNext = sinon.stub();
await proxy.loginRoute(fakeReq, fakeReq, fakeNext).then((_result) => {
result = _result;
});
console.log("result")
console.log(result) // undefined
console.log("result")
});
it("login route test if the stubs are called", async () => {
expect(authService.findByEmail).to.have.been.called // never called
// expect(bcryptStubbing).to.have.been.called // never called
// expect(response.status).to.deep.equal(200); // doesn't work
}).timeout(10000);
after(() => {
sinon.reset()
});
});
Where am i going wrong here in the test?

Why could the findOrCreate plugin create several documents at once?

I'm currently working on a MERN application with following/follower function for the users. I decided to create separate schemas for following and follower relationships detached from user schema.
Follower schema
const mongoose = require('mongoose');
const findOrCreate = require('mongoose-findorcreate');
const ObjectId = mongoose.Schema.Types.ObjectId;
const followerSchema = mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
follower: {
type: [ObjectId],
ref: 'User'
}
});
followerSchema.plugin(findOrCreate);
const Follower = mongoose.model('Follower', followerSchema);
module.exports = { followerSchema, Follower };
Following schema
const mongoose = require('mongoose');
const findOrCreate = require('mongoose-findorcreate');
const ObjectId = mongoose.Schema.Types.ObjectId;
const followingSchema = mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
following: {
type: [ObjectId],
ref: 'User'
}
});
followingSchema.plugin(findOrCreate);
const Following = mongoose.model('Following', followingSchema);
module.exports = { followingSchema, Following };
The problem however starts in my service where followings are created as supposed to. However, for followers mongoose create 6-8 documents at once with userIds that don't even exist in my db.
Here's the code of the followerService (it's the first function)
const { Follower } = require('../models/followerModel');
const { Following } = require('../models/followingModel');
const { User } = require('../models/userModel');
const mongoose = require('mongoose');
exports.changeFollowStatus = async (user, userId) => {
console.log({ userId: userId, user: user._id });
const newFollowing = await Following.findOrCreate({ userId: user._id }, (err, following, created) => {
console.log({following: following});
if (!err && !isFollowing(following, userId)) {
following.following.push(mongoose.Types.ObjectId(userId));
following.save();
User.findByIdAndUpdate(mongoose.Types.ObjectId(userId), {$inc: {follower: 1}});
} else {
const followingIndex = following.following.indexOf(mongoose.Types.ObjectId(userId));
following.following.splice(followingIndex, 1);
following.save();
User.findByIdAndUpdate(mongoose.Types.ObjectId(userId), { $inc: { follower: -1 } });
}
});
const newFollower = await Follower.findOrCreate({ userId: mongoose.Types.ObjectId(userId) }, (err, follower, created) => {
console.log({follower: follower});
if (!err && !isFollower(follower, user._id)) {
follower.follower.push(user._id);
follower.save();
User.findByIdAndUpdate(user._id, { $inc: { following: 1 } });
} else {
const followerIndex = follower.follower.indexOf(user._id);
follower.follower.splice(followerIndex, 1);
follower.save();
User.findByIdAndUpdate(user._id, { $inc: { following: -1 } });
}
});
};
exports.showFollowings = async (userId) => {
const followings = await Following.findOrCreate({ userId: mongoose.Types.ObjectId(userId) });
return followings.following;
};
exports.showFollowers = async (userId) => {
const followers = await Follower.findOrCreate({ userId: mongoose.Types.ObjectId(userId) });
return followers.follower;
};
const isFollowing = (newFollowing, userId) => {
return newFollowing.following.includes(mongoose.Types.ObjectId(userId));
};
const isFollower = (newFollower, userId) => {
return newFollower.follower.includes(userId);
}
Now, my following adding code and follower adding code look almost identical, but for some reason for followers, mongoose creates many more documents. The strange thing is that there is a follower document with the correct userId, but many other with random userIds get created which doesn't happen with followings which works as supposed to.
I also checked whether I pass the correct values and everything seems to be fine. But just for reference, here's the controller function from which I pass the values.
exports.changeFollowingStatus = async (req, res, next) => {
passport.authenticate('jwt', async (err, user, info) => {
if (err) {
console.error({ authError: err });
};
if (info !== undefined) {
console.error({ authError: info.message });
res.status(403).send(info.message);
} else {
console.log({params: req.params});
const userToFollow = req.params.id;
await FollowerService.changeFollowStatus(user, userToFollow);
res.status(200).send({ message: 'success' })
}
})(req, res, next);
};
Could anyone help me with this bug or at least navigate me towards the right direction? I can't seem to find solution to my problem. Thank you all in advance!

Cannot populate fields in express and Mongodb

I have two models Post and Users and I want to populate the posts fields
const PostSchema = new Schema({
text: {
type: String
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
})
const UserSchema = new Schema({
username: {
type: String,
}
})
router.post('/', async (req,res) => {
try {
let joe = new User({username: "joe"})
await joe.save()
let postText = {text: "my name is joe", author: joe._id}
let postByJoe = new Post(postText)
await postByJoe.save()
let users = User.find().populate('author')
res.json({users})
} catch (error) {
console.log(error.message)
res.json(error.message)
}
})
The problem is that I get an error and I don't know what to make of it. I'm not sure at all what the issue is. Any help would be greatly appreciated
"Converting circular structure to JSON\n --> starting at object with constructor 'NativeTopology'\n | property 's' -> object with constructor 'Object'\n | property 'sessionPool' -> object with constructor 'ServerSessionPool'\n --- property 'topology' closes the circle"
What you have in your schema is different, you can only populate the Schema.Types.ObjectId field in your schema. Seems you are trying to populate the author field of the Post model.
Replace this line let users = User.find().populate('author') with this let users = Post.find().populate('user') Your code should be like the one below:
router.post('/', async (req,res) => {
try {
let joe = new User({username: "joe"})
await joe.save()
let postText = {text: "my name is joe", author: joe._id}
let postByJoe = new Post(postText)
await postByJoe.save()
let data = await Post.find().populate('user')
// map through the object returned
const users = data.map((user) => user)
res.json({users})
} catch (error) {
console.log(error.message)
res.json(error.message)
}
})
Read more about mongoose population here

How do I mock Mongoose calls without mocking the callback logic in exec?

I've written a test against my itemService function getItemsForUser to assert on the Items returned from a Mongoose find call. However, my test is currently passing regardless of the user object passed in because it mocks out the whole callback passed to exec.
My question is, how can I mock out only the Items returned by the find and populate but not skip the rest of the logic, so that the test does actually call doUsersContainUser?
itemService.js
const Item = require('../models/item');
exports.getItemsForUser= function(user, cb)
{
Item.find({}).populate('users').exec(function (err, itemList)
{
if (err) { throw next(err) }
else {
cb(null, itemList.filter(i=> doUsersContainUser(i, user._id));
}
});
};
function doUsersContainUser(item, userId) {
return item.users.some(u => hasValue(u, 'user', userId))
}
item.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ItemSchema = new Schema({
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
module.exports = mongoose.model('Item', ItemSchema );
itemServiceTest.js
it('should return all items for user', function () {
const allItems = itemsData.allItemsPopulated();
const user = { id: 10001 };
sinon.mock(Item).expects('find')
.chain('populate', 'users')
.chain('exec')
.returns(null, allItems);
itemService.getItemsForUser(user, function(err, result) {
assert.strictEqual(result, allItems);
});
});
I am using sinon-mongoose for the chained mongoose calls.

Looking for Mongoose "hello world" example

Update: Been some time. But back then decided not to use Mongoose. Main reason being that we couldn't really come up with a great reason for using an ORM when using mongo and javascript.
I've been trying to create a database/model with Mongoose which is basically just a user database where the username is unique. Sounds simple enough, but for some reason I've been unable to do so.
What I've got so far is this:
var mongoose = require('mongoose').Mongoose,
db = mongoose.connect('mongodb://localhost/db');
mongoose.model('User', {
properties: [
'name',
'age'
],
cast: {
name: String,
age: Number
},
//indexes: [[{name:1}, {unique:true}]],
indexes: [
'name'
]
/*,
setters: {},
getters: {},
methods: {}
*/
});
var User = db.model('User');
var u = new User();
u.name = 'Foo';
u.save(function() {
User.find().all(function(arr) {
console.log(arr);
console.log('length='+arr.length);
});
});
/*User.remove({}, function() {});*/
It just doesn't work. The database is created alright, but the username is not unique. Any help or knowledge of what I'm doing wrong?
You need to define the schema. Try this: (
var mongoose = require('mongoose').Mongoose,
db = mongoose.connect('mongodb://localhost/db'),
Schema = mongoose.Schema;
mongoose.model('User', new Schema({
properties: [
'name',
'age'
],
[...]
}));
For Mongoose 2.7 (tested in Node v. 0.8):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var db = mongoose.connect('mongodb://localhost/db');
var User = new Schema({
first_name: String,
last_name: String
});
var UserModel = mongoose.model('User', User);
var record = new UserModel();
record.first_name = 'hello';
record.last_name = 'world';
record.save(function (err) {
UserModel.find({}, function(err, users) {
for (var i=0, counter=users.length; i < counter; i++) {
var user = users[i];
console.log( "User => _id: " + user._id + ", first_name: " + user.first_name + ", last_name: " + user.last_name );
}
});
});
Try giving right path in var mongoose = require('mongoose').Mongoose,
. It worked for me..
#
my code
require.paths.unshift("/home/LearnBoost-mongoose-45a591d/mongoose");
var mongoose = require('mongoose').Mongoose;
var db = mongoose.connect('mongodb://localhost/db');
mongoose.model('User', {
properties: ['first name', 'last name', 'age', 'marriage_status', 'details', 'remark'],
});
var User = db.model('User');
var record = new User();
record.first name = 'xxx';
record.last name = 'xxx';
record.age = 'xxx';
record.marriage_status = 'xxx';
record.details = 'xxx';
record.remarks = 'xxx';
record.save(function() {
User.find().all(function(arr) {
console.log(arr);
console.log('length='+arr.length);
});
});
//User.remove({}, function() {});
Compile it with node filename.js
good luck..
You should define your unique indexes before running your app for the first time. Otherwise, you need to drop your collection and start over. Also, mongoose will not throw an error when you attempt to save { name: 'user1' } when 'user1' already exists.
Learnboost recently uploaded a set of examples https://github.com/LearnBoost/mongoose/tree/master/examples
I am aware this question is 10 years old and the original poster abandoned Mongoose, but since it pops up near the top of Google searches I felt I would provide a fresh answer.
Providing a complete basic example, using Typescript. I have added comments in the
code, where appropriate.
async function mongooseHelloWorld () {
const url = 'mongodb://localhost/helloworld';
// provide options to avoid a number of deprecation warnings
// details at: https://mongoosejs.com/docs/connections.html
const options = {
'useNewUrlParser': true,
'useCreateIndex': true,
'useFindAndModify': false,
'useUnifiedTopology': true
};
// connect to the database
console.log(`Connecting to the database at ${url}`);
await mongoose.connect(url, options);
// create a schema, specifying the fields and also
// indicating createdAt/updatedAt fields should be managed
const userSchema = new mongoose.Schema({
name:{
type: String,
required:true
},
email: {
type: String,
required: true
}
}, {
timestamps: true
});
// this will use the main connection. If you need to use custom
// connections see: https://mongoosejs.com/docs/models.html
const User = mongoose.model('User', userSchema);
// create two users (will not be unique on multiple runs)
console.log('Creating some users');
await User.create({ name: 'Jane Doe', email: 'jane.done#example.abcdef' });
await User.create({ name: 'Joe Bloggs', email: 'jane.done#example.abcdef' });
// Find all users in the database, without any query conditions
const entries = await User.find();
for (let i = 0; i < entries.length; i++) {
const entry = entries[i] as any;
console.log(`user: { name: ${entry.name}, email: ${entry.email} }`);
}
}
// running the code, and making sure we output any fatal errors
mongooseHelloWorld()
.then(() => process.exit(0))
.catch(error => {
console.log(error)
});
Note, this was validated with Mongoose 5.9.26, running against Mongo 4.0.13.

Categories

Resources