bodyParser not executing verify option in Sails.js - sails.js

I'm using body-parser for parsing the data in Sails.js. But is not executing verify option inside bodyParser.json().
Here is my middleware configuration in config/http.js file:
module.exports.http = {
middleware: {
order: [
"cookieParser",
"session",
"bodyParser",
"fileMiddleware",
"router",
"www",
"favicon",
],
bodyParser: (function () {
const bodyParser = require("body-parser");
console.log("body-parser executing");
bodyParser.json({
limit: "50MB",
// for verifyingShopifyWebhook
verify: function(req, res, buf, encoding) {
console.log("buf", buf); //not getting printed on console
if (req.url.search("/shopify/webhooks") >= 0) {
console.log("inside bodyParser verify");
req.rawbody = buf;
}
}
});
bodyParser.urlencoded({ limit: "50MB", parameterLimit: 100000, extended:true });
return bodyParser();
})(),
fileMiddleware: (function (){
const multer = require("multer");
const upload = multer();
return upload.fields([{ name: "tagsFile", maxCount: 1 }]);
})(),
},
};
I'm getting req.rawbody as undefined when trying to access it in the controller function, and it is not even printing the buf object, so it seems like it is not calling the verify function at all.
Also, as middleware needs to be called on every request, then why App is just printing the body-parser executing at launching time and not on every request call?

OK, so finally I have found the answer after referring to the skipper code.
Here is what I did with my body-parser middleware:
bodyParser: (function () {
const bodyParser = require("body-parser");
const JSONBodyParser = bodyParser.json({
limit: "50MB",
// for verifyingShopifyWebhook
verify: function(req, res, buf, encoding) {
if (req.url.search("/shopify/webhooks") >= 0) {
console.log("inside bodyParser verify");
req.rawbody = buf;
}
}
});
const URLEncodedBodyParser = bodyParser.urlencoded({ limit: "50MB", parameterLimit: 100000, extended:true });
return function(req, res, next){
if(req.is("application/x-www-form-urlencoded")){
return URLEncodedBodyParser(req, res, next);
} else {
return JSONBodyParser(req, res, next);
}
}
})()
So rather than returning the bodyParser(), we need to return the particular parser.
Here, if the type is x-www-form-urlencoded then it will return URLEncodedBodyParser, otherwise it will return JSONBodyParser.

Related

Heroku: My routes still pointing to localhost when deployed

I set up my NODE_ENV to production under APP>Settings>Config Vars on Heroku and set up a mongo.json file in my root directory:
{
"development": {
"host": "127.0.0.1:27017",
"dbName": "hillfinder"
},
"production": {
"PRODUCTION_DB_DSN": "mongodb+srv://***credentials***#hillfinder-qjxuo.mongodb.net/production?retryWrites=true&w=majority"
}
}
But it's still pointing to localhost:
This is the my server/index.js:
const express = require('express');
require('dotenv').config();
const nextJS = require('next');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var bodyParser = require('body-parser');
var auth = require('./lib/auth');
var cors = require('cors');
var morgan = require('morgan');
var HttpStatus = require('http-status-codes');
var PORT = process.env.PORT || 8016;
const { isBlockedPage, isInternalUrl } = require('next-server/dist/server/utils');
function NODE_ENVSetter(ENV) {
var environment,
environments = {
production: () => {
environment = process.env.PRODUCTION_DB_DSN;
console.log(`We are currently in the production environment: ${environment}`);
return environment;
},
test: () => {
environment = process.env.TEST_DB_DSN;
console.log(`We are currently in the test environment: ${environment}`);
return environment;
},
default: () => {
environment = process.env.DEVELOPMENT_DB_DSN;
console.log(`We are currently in the development environment: ${environment}`);
return environment;
}
};
(environments[ENV] || environments['default'])();
return environment;
}
var db = NODE_ENVSetter('production');
var mongoose = require('mongoose');
function errorHandler(err, req, res, next) {
// Set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// Log error
console.error(err.stack);
// Render the error page
res.status(err.status || 500);
// Default error message by HTTP code
res.render('error', {
title: HttpStatus.getStatusText(err.status),
message: HttpStatus.getStatusText(err.status)
});
}
async function start() {
const dev = process.env.NODE_ENV !== 'production';
const app = nextJS({ dev });
const server = express();
await app
.prepare()
.then(() => {
mongoose.connect(db, { useNewUrlParser: true });
mongoose.Promise = global.Promise;
mongoose.connection
.on('connected', () => {
console.log(`Mongoose connection open on ${db}`);
})
.on('error', err => {
console.log(`Connection error: ${err.message}`);
});
})
.catch(err => {
console.error(err);
});
server.set('view engine', 'html');
server.use('/uploads', express.static(__dirname + '/uploads'));
server.use(bodyParser.urlencoded({ limit: '50mb', extended: false }));
server.use(bodyParser.json({ limit: '50mb' }));
server.use(morgan('dev'));
server.use(cookieParser());
server.use(
session({
secret: 'very secret 12345',
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
})
);
server.use(auth.initialize);
server.use(auth.session);
server.use(auth.setUser);
console.log('auth.setUser ', auth.setUser);
server.use(cors());
server.use('/users', require('./users'));
server.use('/images', require('./images'));
// Redirect all requests to main entrypoint pages/index.js
server.get('/*', async (req, res, next) => {
try {
const pathName = req.originalUrl;
if (isInternalUrl(req.url)) {
return app.handleRequest(req, res, req.originalUrl);
}
if (isBlockedPage(pathName)) {
return app.render404(req, res, req.originalUrl);
}
req.locals = {};
req.locals.context = {};
const html = await app.renderToHTML(req, res, '/', {});
// Handle client redirects
const context = req.locals.context;
if (context.url) {
return res.redirect(context.url);
}
// Handle client response statuses
if (context.status) {
return res.status(context.status).send();
}
// Request was ended by the user
if (html === null) {
return;
}
app.sendHTML(req, res, html);
} catch (e) {
next(e);
}
});
// error handler
server.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.errorStatus = err.status;
res.locals.errorMessage = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
console.log('err.status ', err.status);
res.status(err.status).send(err.message);
});
server.listen(PORT, err => {
if (err) throw err;
console.log(`> Ready and listening on http://localhost:${PORT}`);
});
}
start();
How would I fix that so I can log on to MongoDB atlas?
Thank you in advance!

How to call http.post using protractor in typescript

I am trying to do http post in protractor. The status of http.post sits in pending status and doesn't return any response.
I am calling a method in onPrepare function under specDone:
jasmine.getEnv().addReporter({
specDone: function(result) {
if (result.status == "failed") {
browser.getCapabilities().then(function(caps) {
var browserName = caps.get("browserName");
browser.takeScreenshot().then(function(png) {
var stream = fs.createWriteStream(
"./reports/screenshots/" +
browserName +
"-" +
result.fullName +
".png"
);
stream.write(new Buffer(png, "base64"));
stream.end();
});
});
}
new PortalData().PushDataToPortal("");
}
});
Below function in called from onPrepare, the API takes the parameter from body. I am using protractor-http-client package for API calls.
export class PortalData {
public PushDataToPortal(result) {
const http: HttpClient = new HttpClient();
const LogFilePathInSharedLocation =
"\\\\10.101.101.11\\temp\\DocStar\\Automation\\TestLogs\\Logs.txt";
http
.post(
someurl,
LogFilePathInSharedLocation,
{ "Content-Type": "application/x-www-form-urlencoded" }
)
.then((response: ResponsePromise) => {
console.log(response);
});
}
Please advise. Thanks!
I would suggest you use Http call in beforeAll instead of onPrepare.
You can try superagent or supertest npm modules:
Example for superagent
const superagent = require('superagent');
// callback
superagent
.post('/api/pet')
.send({ name: 'Manny', species: 'cat' }) // sends a JSON post body
.set('X-API-Key', 'foobar')
.set('accept', 'json')
.end((err, res) => {
// Calling the end function will send the request
});
// promise with then/catch
superagent.post('/api/pet').then(console.log).catch(console.error);
// promise with async/await
(async () => {
try {
const res = await superagent.post('/api/pet');
console.log(res);
} catch (err) {
console.error(err);
}
})();
Example for supertest
const supertest = require('supertest');
const request = supertest(`${baseURL}`);
request.put('/test/sendlocal')
.send(profileAddressData.createData)
.set('Content-Type', 'application/json')
.set('Accept', '*/*')
.expect(200)
.end((err, res) => {
if (err) {
console.error('Error: ', err);
console.error('Response: ', res);
}
});

Admin-on-rest 'Edit Page - Incorrect Element Error // Create Page - element does not exist Error'

I'm using admin on rest with express/mongodb, every things works correctly (CRUD), but I have some errors that have appeared and I have no explanation for that,
when I create an object a notification is displayed "element does not exist" but the object is created correctly and stored in mongodb.
And when I try to update a object (Edit) a notification is displayed "Incorrect element" but the object is been updated correctly and stored in mongodb.
this is my server code:
// =================================================================
// configuration ===================================================
// =================================================================
var port = process.env.PORT || 8060; // used to create, sign, and verify tokens
mongoose.connect(config.database, {
useNewUrlParser: true,
user: config.database_user,
pass: config.database_pass
});
// connect to database
app.set('superSecret', config.secret); // secret variable
// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// use morgan to log requests to the console
app.use(morgan('dev'));
app.use(cors());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Expose-Headers", "X-Total-Count, Content-Range");
next();
});
app.set('etag', false);
// =================================================================
// Post module ================================================
// =================================================================
//-------------------------------
// list all post-----------------
//-------------------------------
app.get('/post', function (req, res) {
Post.find({}, function (err, posts) {
var postsMap = [];
posts.forEach(function (post) {
postsMap.push({ id: post._id, title: post.title, content: post.content})
});
res.setHeader('Content-Range', posts.length);
res.send(postsMap);
});
});
//-------------------------------
// find a postt-----------------
//-------------------------------
app.get('/post/:id', function (req, res) {
Post.findById({_id: req.params.id }, function (err, post) {
res.send(post);
});
});
//-------------------------------
// create new post-----------------
//-------------------------------
app.post('/post', apiRoutes, function (req, res) {
var post = new Post({
title: req.body.content,
content: req.body.title
});
post.save(function(err) {
if (err) throw err;
res.json({ success: true });
});
});
//-------------------------------
// update a post-----------------
//-------------------------------
app.put('/post/:id', apiRoutes, function (req, res) {
if (typeof req.body.content === 'undefined' || typeof req.body.title === 'undefined') {
res.send(400, { message: 'no content provided' })
} else {
Post.update({ '_id': req.params.id }, { title: req.body.title, content: req.body.content }, function (err, post) {
if (err) return res.send(500, { error: err });
return res.send({ message: 'success update', post: post });
});
}
});
//-------------------------------
// delete a post-----------------
//-------------------------------
app.delete('/post/:id', apiRoutes, function (req, res) {
if (typeof req.body.content === 'undefined' || typeof req.body.title === 'undefined') {
res.send(400, { message: 'no content provided' })
} else {
Post.delete({ '_id': req.params.id }, { title: req.body.title, content: req.body.content }, function (err, post) {
if (err) return res.send(500, { error: err });
return res.send({ message: 'success update', post: post });
});
}
});
this is some of my rest client request apicalls :
OPTIONS /post 204 0.096 ms - 0
POST /post 200 2.179 ms - 16
OPTIONS /post/undefined 204 0.098 ms - 0
GET /post/undefined 200 0.288 ms - -
OPTIONS /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 204 0.065 ms - 0
GET /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 200 2.977 ms - 589
OPTIONS /post/5d4819ed1458a84b14295626 204 0.061 ms - 0
GET /post/5d4819ed1458a84b14295626 200 1.411 ms - 76
PUT /post/5d4819ed1458a84b14295626 200 1.422 ms - 64
OPTIONS /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 204 0.071 ms - 0
GET /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 200 1.947 ms - 643[![enter image description here][1]][1]
These two requests are ambiguous for some reason
OPTIONS /post/undefined 204 0.088 ms - 0
GET /post/undefined 200 0.536 ms - -
I'm using simpleRestClient ,
my App.js :
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
options.headers.set('x-access-token', localStorage.getItem('token'));
return fetchUtils.fetchJson(url, options);
};
const restClient = simpleRestClient(API_URL, httpClient);
const App = () => (
<Admin
title="أرشيفارا"
customRoutes={customRoutes}
customReducers={{ theme: themeReducer }}
menu={Menu}
authClient={authClient}
restClient={restClient}
appLayout={Layout}
messages={translations}
>
<Resource name="post" list={PostList} edit={PostEdit} create={PostCreate} />
</Admin>
);
export default App;
This is most probably because mongo react-admin expects all resources to have an id property, and not _id like mongo set it by default.
You'll have to decorate the dataProvider (or modify your API) so that it transform _id into id.
If you're not sure about how to decorate the dataProvider, ping me here and I'll update the answser with an example.
PS: migrate from admin-on-rest to react-admin (the new shiny version) :)
// In src/myRestClient.js
// Convert a MongoDB entity which has an _id property
// to an entity with an id property as react-admin expect
const convertToReactAdmin = ({ _id, ...item }) => ({
id: _id,
...item,
});
// Decorate the simple rest client so that it convert the data from the API
// in the format expected by react-admin
const mongoDBClient = dataProvider => async (type, resource, params) => {
// Execute the API call and wait for it to respond
// It will always return an object with a data, and sometime a total (GET_LIST and GET_MANY)
const { data, total } = await dataProvider(type, resource, params);
switch (type) {
case 'GET_LIST':
case 'GET_MANY':
case 'GET_MANY_REFERENCE':
return {
data: data.map(convertToReactAdmin),
total, // For GET_MANY, total will be undefined
};
case 'GET_ONE':
case 'CREATE':
case 'UPDATE':
case 'DELETE':
return { data: convertToReactAdmin(data) };
default:
return { data };
}
};
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
options.headers.set('x-access-token', localStorage.getItem('token'));
return fetchUtils.fetchJson(url, options);
};
const restClient = simpleRestClient(API_URL, httpClient);
export default MongoDBClient(restClient);
// In src/App.js
import restClient from './myRestClient';
const App = () => (
<Admin
title="أرشيفارا"
customRoutes={customRoutes}
customReducers={{ theme: themeReducer }}
menu={Menu}
authClient={authClient}
restClient={restClient}
appLayout={Layout}
messages={translations}
>
<Resource name="post" list={PostList} edit={PostEdit} create={PostCreate} />
</Admin>
);
export default App;
That's how I fixed this problem, like #GildasGarcia said the problem was in mongodb's id property set by default : _id .
So I created a new json object data that accepts the properties of the mongodb object
//-------------------------------
// find a postt-----------------
//-------------------------------
app.get('/post/:id', function (req, res) {
Post.findById({_id: req.params.id}, function (err, post) {
var data = {
id: req.params.id,
title: post.title,
content:post.content
};
res.status(200).json(data);
});
});

My mongoDB api is getting a webtask.io token error

I'm learning about webtask.io and so I've coded a simple REST api (I'm not using Express.js here but maybe I should). It's a little webtask.io app that connects to an mlab MongoDB database and retrieves todos from a tasklist collection. The issue is that I'm getting this error:
{"code":404,"message":"unable to resolve jtn to webtask token","req_id":"1504385487318.83712"}"
Any idea how to fix this error? Here is a snippet of my code:
var MongoClient = require('mongodb').MongoClient;
...
module.exports =
function (ctx, req, res) {
// write the header and set the response type as a json
res.writeHead(200, { 'Content-Type': 'application/json' });
MongoClient.connect(ctx.data.MONGO_URL, function (err, db) {
if (err) {
res.writeHead(400, { 'Content-Type': 'application/json'});
res.end(JSON.stringify(ERROR_RESPONSE.CONNECT_ERROR));
} else {
switch(req.method) {
case 'GET':
db.collection('tasklist').find({}).sort({"dateAdded" : -1}).toArray(function(err, docs) {
if (err) {
res.writeHead(400, { 'Content-Type': 'application/json'});
res.end(JSON.stringify(ERROR_RESPONSE.GET_ERROR));
} else {
res.end(JSON.stringify(docs));
}
}); //toArray
break;
//post, delete, and put are in here
} //switch
} //else no error
db.close();
}); //Mongo connect
res.end();
} //export function
I decided to try using Express and now I'm able to run my little webtask.io without having to have a web token. I'm not sure why my first try required one and if I find that answer I will post it. Here is my working version:
/* express app as a webtask */
var MongoClient = require('mongodb').MongoClient;
var Express = require('express');
var wt = require('webtask-tools');
var app = Express();
var assert = require('assert');
var ObjectId = require('mongodb').ObjectId;
app.use(require('body-parser').json());
function doCRUD (crudType,req,res) {
MongoClient.connect(req.webtaskContext.secrets.MONGO_URL,function (err, db) {
if (err) {
res.send(JSON.stringify(err));
} else {
switch(crudType) {
case 'GET':
db.collection('tasklist').find({}).sort({"dateAdded" : -1}).toArray(function(err, docs) {
if (err) {
res.send(JSON.stringify(err));
} else {
res.end(JSON.stringify(docs));
}
}); //toArray
break;
case 'POST':
db.collection('tasklist').insertOne({"tasklist" : req.query.todo, "dateAdded" : new Date()}, function(err, r) {
assert.equal(null, err);
assert.equal(1, r.insertedCount);
});
break;
case 'DELETE':
db.collection('tasklist').deleteOne({_id: new ObjectId(req.query.id)},function(err){assert.equal(null,err)});
break;
case 'PUT':
//not implemented for this hack
break;
}
}
});
}
// GET
app.get('*', function (req, res) {
doCRUD('GET',req,res);
});
// POST
app.post('*', function (req, res) {
doCRUD('POST',req,res);
res.end();
});
// DELETE
app.delete('*', function (req, res) {
doCRUD('DELETE',req,res);
res.end();
});
// expose this express app as a webtask-compatible function*/
module.exports = wt.fromExpress(app);
This error appears if you do not specify a valid path.
Try this one for example :
https://wt-666ohgod666ohgod666ohgod666ohgod-0.run.webtask.io/antidisestablishmentarianism666

How to test node.js-mongodb app using Mocha, Chai and Sinon? I am having difficulty with Sinon part

I am new to testing and having difficulty with Sinon stubs and mocks.
How can I write test for 'quote.list_quote' for following senario.
Here is the routes file : quotes.js
const express = require('express');
const request = require('request');
const async = require('async');
const validator = require('validator');
const quote_router = express.Router();
const confg = require("../../confg/confg");
const quote = require("../models/mquotes");
const quotes_model = quote.quotes;
// host name - needs to be set up using the environment variable
const hostname = confg.hostname;
// route for "quotes/"
quote_router.route("/")
// get route : display the random quote
.get((req, res) => {
// display random quote
async.waterfall([
(callback) => {callback(null, {res});},
quote.count_quotes
], check_quote_exist
);
})
// post route : create a new quote
.post((req, res) => {
const doc_json = {author : validator.escape(req.body.quote_author), quote_text : validator.escape(req.body.quote_text)};
const params = {res, doc_json, quote_action : quote.create_quote};
add_edit_quote(params);
})
// put route : edit the quote
.put((req, res) => {
const doc_json = {author : validator.escape(req.body.quote_author), quote_text : validator.escape(req.body.quote_text)};
const params = {res, doc_json, quote_action : quote.update_quote, qid : req.body.quote_id};
add_edit_quote(params);
})
// delete quote : delete the quote
.delete((req, res) => {
const qid = req.body.qid;
const condition = {_id : qid};
async.waterfall([
(callback) => {callback(null, {res, condition});},
quote.delete_quote
], request_quote_list
);
});
// route for "quotes/list" : display quotes list
quote_router.get("/list/", (req, res) => {
// mention the main operation
let operation;
if(req.body.operation != 'undefined') {
operation = req.body.operation;
} else {
operation = "list_quotes";
}
async.waterfall([
(callback) => {callback(null, {res, operation});},
quote.list_quote
], display_quotes_list
);
});
// display the quotes list
const display_quotes_list = (err, params, quotes_list) => {
if (err) {return console.log(err);}
const res = params.res;
const operation = params.operation;
const header_msg = "List of all the quotes";
let alert_msg;
if(operation == "list_quotes") {
alert_msg = null;
} else if(operation == "delete_quote") {
alert_msg = "Quote has been deleted";
}
const params_out = {
page: "quote_list",
title: 'Quotes Manager',
host: hostname,
header_msg,
alert_msg,
quotes_list
};
res.render('index', params_out);
};
// send http request for quote list page
const request_quote_list = (err, params) => {
if (err) {return console.log(err);}
const res = params.res;
const operation = "delete_quote";
request.get('http://' + hostname + '/quotes/list/', {json:{operation}},
(error, response, body) => {
if (!error && response.statusCode == 200) {
res.send(body);
}
});
};
module.exports = quote_router;
This is not complete file. I have included only a portion of it.
And her is the model file : mquotes.js
const mongoose = require('mongoose');
// Define quote schema
const quoteSchema = new mongoose.Schema({
author: String,
quote_text: {type: String, required: true}
},
{timestamps: true}
);
const quote = {};
// Define quotes model
quote.quotes = mongoose.model('quotes', quoteSchema);
// error handler
error_handler = (callback, err, params, return_value) => {
if(err) { return callback(err);}
else {callback(null, params, return_value);}
};
// add quote - create
quote.create_quote = (params, callback) => {
const res = params.res;
const doc_json = params.doc_json;
quote.quotes.create(doc_json, (err, quotes_detail) => {
error_handler(callback, err, {res, operation : 'create_quote'}, quotes_detail);
});
};
// count the number of quotes
quote.count_quotes = (params, callback) => {
quote.quotes.count({}, (err, quotes_count) => {
error_handler(callback, err, params, quotes_count);
});
};
// delete quote - delete - id
quote.delete_quote = (params, callback) => {
quote.quotes.remove(params.condition, (err, query) => {
error_handler(callback, err, params);
});
};
// list quote - find
quote.list_quote = (params, callback) => {
quote.quotes.find({}, (err, quotes_list) => {
error_handler(callback, err, params, quotes_list);
});
};
// find quote by id
quote.quote_by_id = (params, callback) => {
quote.quotes.findById(params.qid, (err, quotes_detail) => {
error_handler(callback, err, params, quotes_detail);
});
};
// returns the detail of random quote
quote.random_qoute = (params, callback) => {
const random_number = params.random_number;
// select one quote after skipping random_number of times
quote.quotes.findOne({}, (err, quotes_detail) => {
error_handler(callback, err, params, quotes_detail);
}).skip(random_number);
};
// update quote - update - id
quote.update_quote = (params, callback) => {
const options = {new: true};
const qid = params.qid;
const update_json = params.doc_json;
quote.quotes.findByIdAndUpdate(qid, {$set: update_json}, options, (err, quotes_detail) => {
params.operation = 'update_quote';
error_handler(callback, err, params, quotes_detail);
});
};
module.exports = quote;
I have installed mocha globally. Now, I want to test the model. Lets take the quote.list_quote function for example.
const mongoose = require('mongoose');
const chai = require('chai');
const sinon = require('sinon');
const expect = chai.expect; // use the "expect" style of Chai
const mquotes = require('./../../app/models/mquotes');
describe('Tests for quote models', () => {
describe("List quote", () => {
it('list_quote() should return list of quotes', () => {
});
});
});
Can anyone tell me about my coding practice too. I mean the way I use functions and modules.
First of all, you should try to use statics methods. And after that, you should use sinon-mongoose and sinon-as-promised if you want to use Promise in mongoose.
And this is my sample code and test with mocha, chai, and sinon. Hope useful for you.
model.js
var Schema = new mongoose.Schema({
name: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
Schema.statics.findByName = function(name, cb) {
this.findOne({
name: name
})
.exec()
.then(function getTemplate(template) {
if (!template) {
var error = new Error('Not found template by name: "' + name + '"');
error.status = 404;
return cb(error);
}
return cb(null, template);
})
.catch(function catchErrorWhenFindByTemplateName(error) {
error.status = 500;
return cb(error);
});
}
module.exports = mongoose.model('model', Schema);
test.js
var expect = require('chai').expect,
sinon = require('sinon'),
mongoose = require('mongoose');
require('sinon-as-promised');
require('sinon-mongoose');
var Model = require('../../app/models/model');
describe('Model', function () {
describe('static methods', function () {
describe('#findByName', function () {
var ModelMock;
beforeEach(function () {
ModelMock = sinon.mock(Model);
});
afterEach(function () {
ModelMock.restore();
});
it('should get error status 404 if not found template', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves(null);
Model.findByName(name, function (error) {
expect(error.status).to.eql(404);
ModelMock.verify();
done();
});
});
it('should get message not found template if name is not existed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves(null);
Model.findByName(name, function (error) {
expect(error.message).to.match(/Not found template by name/gi);
ModelMock.verify();
done();
});
});
it('should get template when name is existed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves('SUCCESS');
Model.findByName(name, function (error) {
expect(error).to.be.null;
ModelMock.verify();
done();
});
});
it('should get error status 500 when model crashed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.rejects(new Error('Oops! Crashed'));
Model.findByName(name, function (error) {
expect(error.status).to.eql(500);
ModelMock.verify();
done();
});
});
});
});
});