ES6 Getter returning empty object - javascript

I have created a method that grabs the image data based on another method. As you can see the uploadAvatar() method stores the object back from Cloudinary into a constructor object this._userImage;. Then my getUserImage() method should return the user image data but instead it returns {} that when I log it to the console :P.
ImageUploader.js
'use strict';
const cloudinary = require('cloudinary');
class ImageUploader {
constructor(imageObj) {
this.imageObj = imageObj;
this._apiKey = "key";
this._apiSecret = "secret";
this.config = cloudinary.config({
cloud_name: 'name',
api_key: this._apiKey,
api_secret: this._apiSecret
});
this._userImage = {}; // where user image object should be stored
}
* uploadAvatar(path) {
cloudinary.uploader.upload(path, (data) => {
this._userImage = data; // where I overwrite the constructor obj
});
}
getUserImage() {
return this._userImage; // this returns `{}` when I log it in UsersController.js
}
}
module.exports = ImageUploader;
UsersController.js
'use strict'
const Validator = require("../Helpers/ValidatorHelper.js");
const ImageUploader = require("../Helpers/ImageUploader.js");
class UsersController {
* registerView (request, response) {
yield response.sendView('users.register');
}
* register (request, response) {
const user = request.only('display_name', 'username', 'email', 'password', 'confirm_password', 'console');
var avatar = request.file('avatar');
let validator = new Validator(user, avatar);
let imageUpload = new ImageUploader(avatar);
let avatarStatus = yield validator.validateAvatar();
var img;
if (avatarStatus) { // is true
yield imageUpload.uploadAvatar(avatar.file.path);
} else { // is false
// pick a random avatar
}
console.log(imageUpload.getUserImage());
return response.status(200).json({
user: user,
avatar: imageUpload.getUserImage()
});
}
}
module.exports = UsersController

Not sure but here you could lost your this context.
cloudinary.uploader.upload(path, (data) => {
this._userImage = data; // where I overwrite the constructor obj
});
Check if 'this' there still links to your UsersController object

Related

Unable to define and use heleper function value as global variable in node js

I am trying to showing notification in my project so thats why i am creating helperFunction and call in main server.js file and define as global variable but when it return value [object promise],If i call helper function in my other controller it return value but as global variable it return object promise
************ helper function *************
const fs = require('fs');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const models = require(appRoot + '/models');
const helpersService = () => {
const getNotification = async () => {
var findStatus = 0;
if (findStatus != '' && findStatus != null) {
var whereOptions = {
is_read: {
[Op.iLike]: '%' + findStatus + '%'
}
}
} else {
var whereOptions = {}
}
let getNotifications = await models.Notifications.findAll({
include: [{
attributes: ['id', 'full_name', 'email'],
model: models.Users,
as: 'user'
}],
where: whereOptions,
raw: true,
});
return getNotifications;
};
return {
getTranslatedText,
notificationStatus,
getNotification,
};
};
module.exports = helpersService;
**************************************
*************** server.js *************
global.appRoot = path.resolve(__dirname);
const helpersService = require(appRoot + '/services/helpers.service');
global.notifcations = helpersService().getNotification();
****************************************
**************view.ejs*******************
<li style="padding-top: 0px; border-top: none;">
<a class="dropdown-item" href="/admin/users/logout">
<%= notifcations %></a></li>
***************************************
*********** output is *****************
[object promise`]`
You are actually calling your getNotification function and setting the return value to the the globals object, thats why it says [object Promise].
You shlould pass only the function:
global.notifcations = helpersService().getNotification;
At fitst you must add await to your function
global.notifcations = await helpersService().getNotification();
And then your notifications is an array of object and you can't see this directly, use this method
for Example notifications[0].name

JavaScript: Jest doesn't recognise static functions

I'm following a tutorial which is using jest to test the javascript. The instructor created a static function called genesis() on a class called Block and it worked for him just fine, but when I tried to do it I got TypeError: block.genesis is not a function. If I remove the static keyword it recognises the function and the test passes.
Here is the class:
const { GENESIS_DATA } = require('./config');
class Block {
constructor({ timestamp, lastHash, hash, data }) {
this.timestamp = timestamp;
this.lastHash = lastHash;
this.hash = hash;
this.data = data;
}
static genesis() {
return new Block(GENESIS_DATA);
}
}
module.exports = Block;
And the test:
const Block = require('./block');
const { GENESIS_DATA } = require('./config');
describe('Block', () => {
const timestamp = 'a-date';
const lastHash = 'a-hash';
const hash = 'another-hash';
const data = ['blockchain', 'data'];
const block = new Block({ timestamp, lastHash, hash, data });
describe('genesis()', () => {
const genesisBlock = block.genesis();
it('returns a block instance', () => {
expect(genesisBlock instanceof Block).toBe(true);
});
it('returns the genesis data', () => {
expect(genesisBlock).toEqual(GENESIS_DATA);
});
});
});
The genesis method is part of the class, not the instance. You want to call Block.genesis() instead of block.genesis()

Nodejs Require Class returning {}

So I'm creating a singleton class and when I require it from my server.js file it works fine, but when I require it from another file it returns as undefined. I'll try to post relevant code but some will have to be cut out due to work.
server.js
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
const config = require('config');
const FBConfigsListener = require('./server/amq_listeners/fb_configs.listener');
const FBConfigs = require('./server/models/FBConfigs');
//Api file for interacting with mongodb
const api = require('./server/routes/api.routes');
//Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
//Angular Dist output folder
app.use(express.static(path.join(__dirname, 'dist')));
//Api location
app.use('/api', api);
//Send all other requests to angular
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
//set port
var port = config.get('webserver.port');
app.set('port', port);
const server = http.createServer(app);
server.listen(port, () => console.log(`Running on localhost:${port}`));
models/FBConfigs.js
var ConfigModel = require('./config');
var config = require('config');
var _ = require('lodash');
var FBConfigsListener = require('../amq_listeners/fb_configs.listener');
var AMQAdapter = require('../adapters/amq.adapter');
var uniqid = require('uniqid');
const connectionOptions = config.get('activemq.connectionOptions');
class FBConfigs {
constructor() {
console.log(config.get('environments'));
this.listener = FBConfigsListener;
this.configs = {};
this.unique_keys = ['id'];
this.update_topic = '/topic/fusebuilder.update.config.';
console.log(FBConfigsListener);
//AMQ Client
this.amq_client = AMQAdapter.getInstance(connectionOptions.host, connectionOptions.port);
}
add(key, config) {
if (!(key in this.configs)) {
this.configs[key] = new ConfigModel(this.unique_keys);
}
this.configs[key].add(config);
}
get(key) {
let configs_json = {};
if (key) {
configs_json[key] = JSON.parse(this.configs[key].toString());
} else {
for (let key in this.configs) {
configs_json[key] = JSON.parse(this.configs[key].toString());
}
}
return configs_json;
}
updateByID(key, id, input_config) {
let configs = this.configs[key].get();
for (let config of configs) {
if (input_config.id === config.id) {
this.update(key, _.merge(config, input_config));
}
}
}
//Send update to config topic
update(key, config) {
let topic = this.update_topic + key;
var update_object = {};
if (Array.isArray(config)) {
update_object[key] = [...config];
} else {
update_object[key] = [config];
}
console.log(`Sending ${key} update:${JSON.stringify(update_object)}`);
this.amq_client.sendMessage(topic, update_object);
}
copyTo(key, id, env) {
let selected_env = config.get('environments.' + env);
// let tmp_amq_client = new AMQAdapter(selected_env.host, selected_env.port);
let selected_config = this.configs[key].getByID(id);
console.log(this);
if (key === 'fuses') {
console.log('In FBConfig Copy to for fuses');
const get_fuse_topic = '/topic/fusebuilder.get_fuse';
const tran_id = uniqid();
const sendObj = { fuseName: id, tran_id };
this.amq_client.sendMessage(get_fuse_topic, sendObj);
var startTime = process.hrtime()[0];
var timeout = false;
while (!this.listener.get_copy_fuse_data(tran_id)) {
console.log('Waiting for config');
sleep(100);
if (process.hrtime()[0] - startTime > 3) {
console.log('Timed out');
timeout = true;
break;
}
}
console.log(JSON.stringify(FBConfigsListener.get_copy_fuse_data(tran_id)));
} else {
tmp_amq_client.sendMessage(this.update_topic, selected_config);
}
console.log(`Copy ${key} id:${id} to ${env}`);
}
}
module.exports = new FBConfigs();
amq_listener/fb_configs.listener.js
const config = require('config');
var AMQAdapter = require('../adapters/amq.adapter');
var FBConfigs = require('../models/FBConfigs');
**removed for work**
class FBConfigsListener {
constructor() {
this.instance;
this.copy_fuse_data = {};
//AMQ Client
this.amq_client = AMQAdapter.getInstance(connectionOptions.host, connectionOptions.port);
//Subscribe to configs
this.amq_client.subscribe(config_subscribe_topic, this.config_topic_callback.bind(this));
//Request Tables
this.amq_client.sendMessage(config_request_topic, { tables: config_tables });
//Subscribe to Copy Fuse topic
this.amq_client.subscribe(subscribe_fuse_copy_topic, this.copy_fuse_callback.bind(this));
}
config_topic_callback(err, message) {
let dest = this.amq_client.getDestination(message);
let key = this.get_key_from_topic(dest);
this.amq_client.readMessage(message, body => {
let configs = JSON.parse(body);
if (key in configs) {
for (let config of configs[key]) {
FBConfigs.add(key, config);
}
}
});
}
copy_fuse_callback(err, message) {
this.amq_client.readMessage(message, body => {
const config = JSON.parse(body);
this.copy_fuse_data[config.tran_id] = config;
});
}
//Get Key from the topic and convert using key map if needed
get_key_from_topic(topic) {
let key = topic.split('.')[topic.split('.').length - 1];
key = key in key_map ? key_map[key] : key;
return key;
}
get_copy_fuse_data(id) {
if (id in this.copy_fuse_data) {
return this.copy_fuse_data[id];
} else {
return false;
}
}
}
module.exports = new FBConfigsListener();
Error happens in FBConfigs. FBConfigsListener returns {} so all functions in there are undefined. Even if I do console.log(require('../amq_listeners/fb_configs.listener')) it prints {} But doing the same thing in server.js (with updated path) it prints the module.
Also tips on how to improve my coding style would be appreciated too.
Edit
So I found out that I have a circular dependency between these classes. How can this be fixed while allowing me to call one from the other.
I would suggest you to instantiate your dependencies firstly and store them in some object which you can pass then to your dependent classes. The structure can be
factories/services.js
/*
* Instantiates passed services and passes injector object to them
*/
module.exports = function createServices(injector, services) {
return Object.entries(services)
.reduce((aggregator, [name, serv]) => {
const name_ = camelCase(name);
aggregator.set(name_, new serv(injector));
return aggregator;
}, new Map());
};
lib/service.js
/**
* Base class for classes need any injections
*/
module.exports = class Service {
constructor(injector) {
this.injector = injector;
}
get dependencies() {
return this.injector.dependencies;
}
/*
* Background jobs can be ran here
*/
async startService() {}
/*
* Background jobs can be stopped here
*/
async stopService() {}
};
lib/injector.js
const Service = require('./service');
/*
* Contains all dependencies
*/
module.exports = class Injector {
constructor() {
this.services = new Map();
this._dependencies = {};
}
has(name) {
return this.services.has(name);
}
register(name, service) {
if (this.has(name)) {
throw new Error(`Service ${name} already exists`);
}
if (service instanceof Service === false) {
throw new Error('Argument #2 should be an instance of Service');
}
this.services.set(name, service);
this._dependencies[name] = service;
}
unregister(name) {
if (! this.has(name)) {
throw new Error(`Service ${name} not found`);
}
this.services.delete(name);
delete this._dependencies[name];
}
get dependencies() {
return { ...this._dependencies };
}
/*
* Starts all registered services
*/
async start() {
for (let service of this.services.values()) {
await service.startService();
}
}
/*
* Stops all registered services
*/
async stop() {
for (let service of this.services.values()) {
await service.stopService();
}
}
};
Then import, initialize and bind your services in the main file (don't forget to export just a class, not an object like you do it now).
server.js
const createServices = require('./factories/services.js');
const injector = require('./lib/injector');
const Injector = new injector();
const services = createServices(Injector, [require('./server/amq_listeners/fb_configs.listener'), require('./server/models/FBConfigs')]);
services.forEach((service, name) => {
Injector.register(name, service);
});
// Start services
Injector.start();
Inherit required classes to Service class and you will get an access to all dependencies there (don't forget to call super() from constructor). Like
models/FBConfigs.js
const Service = require('../lib/service');
class FBConfigs extends Service {
constructor(injector) {
super(injector);
const { FBConfigsListener } = this.dependencies;
...your code here
}
async startService() {
...run bg job or init some connection
}
async stopService() {
...stop bg job or close some connection
}
}
module.exports = FBConfigs;
Also you can pass some config object to createServices (I didn't include it here) with keys equal to service names and values containing config object and pass config to appropriate service.
It is caused by that circular dependency. You should avoid it or used very carefully.
In your case the fix is probably pretty simple, move the line var FBConfigs = require('../models/FBConfigs'); from listener at the end of the file as the last line (yes, even after the module.exports).
Edit: Actually it maybe is not enough as I checked the code more in detail. As you are not using Listener in FBConfig constructor, you can create method assignListener, remove this.listener from that constructor and call it later in server.js which will do the this.listener
Or the last solution, which is also "best practice". Do not export the instances. Export the classes only. Then in server.js create these instances after both are required.

Sinon.js: How to mock a object constructed with new?

I have this code:
var async = require('async'),
util = require('util');
var Parse = require('parse/node');
function signup(userInfo, callback) {
var username = userInfo.username,
email = userInfo.email,
password = userInfo.password;
var user = new Parse.User();
user.set('username', username);
user.set('email', email);
user.set('password', password);
user.signUp(null, {
success: (user) => {
// console.log('BaaS Signup success ' + util.inspect(user));
callback(null, user);
},
error: (user, error) => {
// console.log('BaaS Signup error ' + util.inspect(error));
callback(JSON.stringify(error));
}
});
}
I want to unit test it, and as such need to be able to test the content of the success and error function.
If user was a library, I could use proxyquire and put a stub returning a promise and be done with it, however, user is constructed.
How can I stub Parse.User() so that I can control the resulting object?
This seems to work:
// test `success` callback
let testUser = { user : 'test' };
let stub = sinon.stub(Parse.User.prototype, 'signUp')
.yieldsTo('success', testUser);
You should be able to stub the constructor just like you'd stub any other funtion, and just return your fake object:
sinon.stub(Parse, "User")
.andReturn(fakeUser);
let u = new Parse.User();
//u === fakeUser
While it is ugly as hell, I ve succeed in making my test work with this:
var chai = require('chai'),
expect = chai.expect,
util = require('util');
var proxyquire = require('proxyquire').noPreserveCache();
var sinon = require('sinon');
describe('SignUp', () => {
it('Expect to sign up the user and return the user', (done) => {
var stub = {
parse: {
User: function User() {
this.set = () => {};
this.signUp = (a, c) => {
c.success({id: 'UserID'});
};
}
}
},
baas = proxyquire('./../baas.js', {
'parse/node': stub.parse
});
baas.signup({
username: 'User',
email: 'user#test.com',
password: 'Password'
}, (err, user) => {
try {
expect(err).to.not.be.ok;
expect(user).to.be.ok;
done();
} catch(err) {
done(err);
}
});
});
});
The answer I received made me realize I don't use sinon like I should, and all my test are built by hacking around part of proxyquire and sinon together.
I will need to reread the documentation.

Sinon async test: array is not filled in before push happens

I write test for AngularJS application in Sinon & Chai. I do $httpbackend request and I have a fake array of users - backendUsersResponse. It's available in my .spec.js but it's not filled in yet when I call the function openSettings and so my users are an empty array in my controller.
How & where can I tell my code to wait till array is filled in? How do you test async javascript stuff like this?
Thanks in advance!
Controller code:
export default class UserAccountsController {
constructor(UserModel, userSettingsDialog) {
this.UserModel = UserModel;
this.userSettingsDialog = userSettingsDialog;
this.users = [];
UserModel.query()
.then((users) => {
this.users = users;
});
}
openSettings(user) {
const userToUpdate = user || new this.UserModel();
this.userSettingsDialog.openDialog(userToUpdate)
.then((data) => {
const cmd = data.value;
if (cmd === 'saveSettings') {
const isNew = angular.isUndefined(userToUpdate.id);
userToUpdate.$save();
if (isNew) {
this.users.push(userToUpdate);
}
}
});
}
}
UserAccountsController.$inject = [ 'UserModel', 'userSettingsDialog'];
Test
import { expect } from 'chai';
import sinon from 'sinon/pkg/sinon';
describe('userAccounts Controller', () => {
'use strict';
beforeEach(angular.mock.module('myproject'));
let $httpBackend = null;
let $q = null;
let UserModel = null;
let userSettingsDialog = null;
beforeEach(inject(function(_$httpBackend_, _UserModel_, _$q_) {
$httpBackend = _$httpBackend_;
UserModel = _UserModel_;
$q = _$q_;
}));
describe('openSettings', () => {
let $controller;
let backendUsersResponse;
beforeEach(inject(function(_$controller_) {
userSettingsDialog = {
openDialog: sinon.stub(),
};
$controller = _$controller_;
backendUsersResponse = {
users: [
{
id: 1,
name: 'John',
role: 'admin',
},
{
id: 2,
name: 'Diego',
role: 'editor',
},
],
};
}));
it('should check if new user is pushed to an array', () => {
// arrange
const ctrl = $controller('UserAccountsController', {
userSettingsDialog: userSettingsDialog,
});
const user = new UserModel({
name: 'Lander',
});
const data = {
value: 'saveSettings',
};
$httpBackend.expectGET('api/users').respond(200, backendUsersResponse);
userSettingsDialog.openDialog.withArgs(user).returns($q.resolve(data));
$httpBackend.expectPOST('api/users').respond(200, user);
// act
ctrl.openSettings(user);
$httpBackend.flush();
// assert
expect(userSettingsDialog.openDialog).to.have.been.called;
expect(user.name).to.equal('Lander');
expect(ctrl.users).not.to.be.empty;
expect(ctrl.users.length).to.be(3); //<-- this one fails, array length is always 2, user is not added
});
});
});

Categories