Stubbing virtual attributes of Mongoose model - node.js

Is there a way to stub a virtual attribute of a Mongoose Model?
Assume Problem is a model class, and difficulty is a virtual attribute. delete Problem.prototype.difficulty returns false, and the attribute is still there, so I can't replace it with any value I want.
I also tried
var p = new Problem();
delete p.difficulty;
p.difficulty = Problem.INT_EASY;
It didn't work.
Assigning undefined to Problem.prototype.difficulty or using sinon.stub(Problem.prototype, 'difficulty').returns(Problem.INT_EASY);
would throw an exception "TypeError: Cannot read property 'scope' of undefined", while doing
var p = new Problem();
sinon.stub(p, 'difficulty').returns(Problem.INT_EASY);
would throw an error "TypeError: Attempted to wrap string property difficulty as function".
I am running out of ideas. Help me out! Thanks!

mongoose internally uses Object.defineProperty for all properties. Since they are defined as non-configurable, you can't delete them, and you can't re-configure them, either.
What you can do, though, is overwriting the model’s get and set methods, which are used to get and set any property:
var p = new Problem();
p.get = function (path, type) {
if (path === 'difficulty') {
return Problem.INT_EASY;
}
return Problem.prototype.get.apply(this, arguments);
};
Or, a complete example using sinon.js:
var mongoose = require('mongoose');
var sinon = require('sinon');
var problemSchema = new mongoose.Schema({});
problemSchema.virtual('difficulty').get(function () {
return Problem.INT_HARD;
});
var Problem = mongoose.model('Problem', problemSchema);
Problem.INT_EASY = 1;
Problem.INT_HARD = 2;
var p = new Problem();
console.log(p.difficulty);
sinon.stub(p, 'get').withArgs('difficulty').returns(Problem.INT_EASY);
console.log(p.difficulty);

As of the end of 2017 and the current Sinon version, stubbing only some of the arguments (e.g. only virtuals on mongoose models) can be achieved in the following manner
const ingr = new Model.ingredientModel({
productId: new ObjectID(),
});
// memorizing the original function + binding to the context
const getOrigin = ingr.get.bind(ingr);
const getStub = S.stub(ingr, 'get').callsFake(key => {
// stubbing ingr.$product virtual
if (key === '$product') {
return { nutrition: productNutrition };
}
// stubbing ingr.qty
else if (key === 'qty') {
return { numericAmount: 0.5 };
}
// otherwise return the original
else {
return getOrigin(key);
}
});
The solution is inspired by a bunch of different advises, including one by #Adrian Heine

Related

How can I store the value of a promise and use it once resolved?

I am currently developing an app which interacts with uniswap, and I have developed a Wrapper class to contain the info and variables I'll need about some pair (e.g DAI/WETH).
As some of this values are asynchronous, I have coded a build() async function to get those before calling the constructor, so I can store them. I want to store the result of this build function, which is an instance of the class I have defined, inside a variable to use it later, but I need to know whether the Promise that that build function returns is resolved before using it, so how can I make it?
Here is the code of the class:
'use strict'
const { ChainId, Fetcher, WETH, Route, Trade, TokenAmoun, TradeType, TokenAmount } = require('#uniswap/sdk')
const { toChecksumAddress } = require('ethereum-checksum-address')
const Web3 = require('web3')
const web3 = new Web3()
const chainId = ChainId.MAINNET;
let tok1;
let tok2;
let pair;
let route;
let trade;
class UniswapTokenPriceFetcher
{
constructor(async_params)
{
async_params.forEach((element) => {
if (element === 'undefined')
{
throw new Error('All parameters must be defined')
}
});
this.trade = async_params[0];
this.route = async_params[1];
this.pair = async_params[2];
this.tok1 = async_params[3];
this.tok2 = async_params[4];
}
static async build(token1, token2)
{
var tok1 = await Fetcher.fetchTokenData(chainId, toChecksumAddress(token1))
var tok2 = await Fetcher.fetchTokenData(chainId, toChecksumAddress(token2))
var pair = await Fetcher.fetchPairData(tok1, tok2)
var route = new Route([pair], tok2)
var trade = new Trade(route, new TokenAmount(tok2, web3.utils.toWei('1', 'Ether')), TradeType.EXACT_INPUT)
return new UniswapTokenPriceFetcher([trade, route, pair, tok1, tok2])
}
getExecutionPrice6d = () =>
{
return this.trade.executionPrice.toSignificant(6);
}
getNextMidPrice6d = () =>
{
return this.trade.nextMidPrice.toSignificant(6);
}
}
module.exports = UniswapTokenPriceFetcher
Thank you everybody!
EDIT: I know Uniswap only pairs with WETH so one of my token variables is unneccesary, but the problem remains the same! Also keep in mind that I want to store an instance of this class for latter use inside another file.
You should either call the build function with await
const priceFetcher = await UniswapTokenPriceFetcher.build(token1, token2)
or followed by then
UniswapTokenPriceFetcher.build(token1, token2).then(priceFetcher => {...})
I don't see any other way.

Return from then - nodejs

I am sort of new to NodeJS and I'm learning as I code but I can't wrap my head around Promise/then.
Here is the piece of code - I'm using a library function to Read database values.
var collection = 'Students';
var query = {};
query.name = 'name';
//readFromDatabse returns -{Function} a promise to return the document found, or null if not found
var temp = readFromDatabase(collection, query).then(function(studentData) {
var result = {
resultDetails: {
username: studentData.username,
password: studentData.password
}
};
return callback(null,resultDetails);
});
but when I read see the values in temp, it contains {"isFulfilled":false,"isRejected":false}!! how can I get the result details into temp?
You have to think of Promises as containers for values. readFromDatabase returns such a container, which might eventually hold the requested value unless the query fails. Your temp variable points to the container, but not the response. The properties isFullfilled and isRejected are attributes of the Promise telling you that it has neither been resolved with a value or rejected with an error.
To get to the response you have to use the then method. The function you register there will be called for you, when the query yields a result or an error.
var collection = 'Students';
var query = {};
query.name = 'name';
var temp = null;
readFromDatabase(collection, query).then(function(studentData) {
var result = {
resultDetails: {
username: studentData.username,
password: studentData.password
}
};
temp = result;
});
// temp is still null here

Promisify trying to connect class with callback [duplicate]

I am trying to refactory my nodejs server using promises with Bluebird library, but I am stuck in a simple problem.
After to get the users from my db, I want to list all notification class associated with this user:
Bad Way (working...)
adapter.getUsers(function(users){
users.rows.forEach(function(item){
user = item.username;
adapter.getNotifications(user, function(notificationList){
console.log(notificationList);
})
});
});
Elegant Tentative Way (not working...)
var getNotifications = Promise.promisify(adapter.getNotifications);
adapter.getUsers().then(function(users) {
users.rows.forEach(function(item){
var dbUser = "sigalei/" + item.value.name;
console.log(dbUser);
return getNotifications(dbUser);
});
}).then(function(result){
console.log(result);
console.log("NOTIFICATIONLIST");
});
However when I execute this code I get this error inside my getNotification method:
Unhandled rejection TypeError: Cannot read property 'nano' of undefined
at Adapter.getNotifications (/Users/DaniloOliveira/Workspace/sigalei-api/api/tools/couchdb-adapter.js:387:30)
at tryCatcher (/Users/DaniloOliveira/Workspace/sigalei-api/node_modules/bluebird/js/main/util.js:26:23)
EDIT
After the user2864740`s precious comments, I noticed that the error is related with some scope problem. So, why after to use promisify method, the method dont getNotifications recognize the "this" env variable?
var Adapter = module.exports = function(config) {
this.nano = require('nano')({
url: url,
request_defaults: config.request_defaults
});
};
Adapter.prototype.getNotifications = function(userDb, done) {
var that = this;
console.log(that);
var userDbInstance = that.nano.use(userDb);
userDbInstance.view('_notificacao', 'lista',
{start_key: "[false]", end_key: "[false,{}]"},
function(err, body) {
if(err){ done(err); }
done(body);
});
};
This is just the very common problem of calling "unbound" methods.
You can pass the context as an option to Promise.promisify to have it bound:
var getNotifications = Promise.promisify(adapter.getNotifications, {context: adapter});
Alternatively, you'd need to .bind() the method, or call the new getNotifications function on the adapter (using .call()). You might also consider using Promise.promisifyAll(adapater) and then just calling adapter.getNotificationsAsync(…).
Notice that this still doesn't work. You cannot simply create promises in a loop - you need to await them explicitly and return a promise from the then callback, otherwise just the undefined value you returned will be passed to the next callback immediately.
adapter.getUsers().then(function(users) {
return Promise.all(users.rows.map(function(item){
var dbUser = "sigalei/" + item.value.name;
console.log(dbUser);
return getNotifications(dbUser);
}));
}).then(function(results) {
for (var i=0; i<results.length; i++)
console.log("result:", results[i]);
});
Instead of Promise.all(users.rows.map(…)), in Bluebird you can also use Promise.map(users.rows, …).
What about simply
var getNotifications = Promise.promisify(adapter.getNotifications.bind(adapter));
or possibly
var getNotifications = Promise.promisify(function () {
return adapter.getNotifications.apply(adapter, arguments);
});
?
I'm not sure I understand your problem well, but this should make sure this is bound and not undefined when you do return getNotifications(dbUser);

mongoose model inheritance within javascript prototypal object

Suppose you have a route initialization like this required in your main:
module.exports = function(app) {
for (var name in names) {
var schema = new Schema({}) // schema that accepts anything
, m = mongoose.model(name, schema)
, controller = new TextController(m)
app.get('/path', controller.create.bind(controller))
// etc, etc
And TextController is defined externally as:
var TextController = function(Model) {
this.Model = Model
}
TextController.prototype.create = function(req, res) {
var aDoc = this.Model({ // this is the problematic bit
title: req.body.title
, content: req.body.content})
aDoc.save(function(err) {...})
}
For some reason, mongo saves this as an empty document even though the title and content params are the expected strings. As expected, this.Model is some sort of mongoose object, but it seems to be rejecting the save or the instantiation. Any ideas or suggestions?
Note: I added the controller.method.bind(controller) because it was the only way (I knew of) to get access to this.Model.
Edit: I've also tried the following:
var TextController = function(myCollection) {
this.myCollection = myCollection
this.list = function(req, res) {
this.myCollection.find({}, function { ... })
}
}
And also tried passing in the name and initializing the model within the scope of the function function(name) { this.myCollection = mongoose.model(name) ... }
This turns out to be unrelated to javascript prototypes and completely due to how mongoose does Mixed Type Schemas:
In order to tell mongoose the document has changed you need to markModified(field)
example here: http://mongoosejs.com/docs/schematypes.html#mixed

wrapping node-memcached with deferred.promisify error

I am trying to wrap the node-memcached api with deferred's promisify in order to simplify my nested callbacks.
When I try to call the promisified function I just get "TypeError: Cannot read property 'namespace' of undefined".
Memcached = require('memcached');
var memcache = new Memcached('localhost:11211');
var add = deferred.promisify(memcache.add);
add('myKey', 'myVal', 0)(function(result) {
...
});
I can't seem to find anyone else trying to wrap node-memcached, or getting my same error. Any insight into what may be going wrong? Or maybe even a push into a better direction if this is imperfect?
Thanks!
EDIT::
Just wanted to response that I found the best solution I could for now by doing some digging.
It seems that deferred.promisify calls the passed function with its own scope (this), instead of the context of the function that is passed in.
Using my own promisfy function appears to fix the issue (idea from http://howtonode.org/promises):
function promisify(fn, context) {
return function() {
var def = deferred();
var args = Array.prototype.slice.call(arguments);
args.push(function(err, val) {
if (err !== null) {
return def.reject(new Error(err));
}
return def.resolve(val);
});
fn.apply(context, args);
return def.promise;
};
}
When promisify instances members you should bind it to this instance like:
Memcached = require('memcached');
var memcache = new Memcached('localhost:11211');
var add = deferred.promisify(memcache.add.bind( memcache ) );
add('myKey', 'myVal', 0)(function(result) {
...
});

Resources