Sails.js - async controller fails to bind to routes - javascript

Before Node.js 7.0 was released my Sails.js controllers looked something similar to:
const _async = require('asyncawait/async');
const _await = require('asyncawait/await');
...
get: _async (function (req, res) {
try {
let item = _await (Items.find({
id: req.params.id
}));
if (!item.length) {
return res.notFound("The item does not exist");
}
return res.ok(item[0]);
} catch (error) {
return res.serverError(error);
}
}),
Everything used to run perfectly. After the release of Node.js 7.0, I changed it to:
get: async function (req, res) {
try {
let item = await Items.find({
id: req.params.id
});
if (!item.length) {
return res.notFound("The item does not exist");
}
return res.ok(item[0]);
} catch (error) {
return res.serverError(error);
}
},
But it logs error:
error: In items.get, ignored invalid attempt to bind route to a
non-function controller: { [Function: get] _middlewareType: 'ACTION:
items/get' } for path: /v1/items/:id and verb: get
Edit:
Looks like a bug in underscore lib used by sails:
if ( !_.isFunction(originalFn) ) {
sails.after('lifted', function () {
sails.log.error(
'In '+controllerId + '.' + actionId+', ignored invalid attempt to bind route to a non-function controller:',
originalFn, 'for path: ', path, verb ? ('and verb: ' + verb) : '');
});
return;
}
Logged an issue: https://github.com/balderdashy/sails/issues/3863

Related

Jest mock not always works in async test

I have a function and I want to test it using Jest.
function handleRegister() {
return (req, res) => {
try {
const credentials = {
login: req.body.email,
password: req.body.password
}
res.status(201).send({ msg: 'User registration achieved successfully' }) //LINE 10
res.status(201).send({ msg: 'User registration achieved successfully' }) //LINE 11
auth.register(credentials, (err, result) => {
console.log('register', auth.getUsers())
if (result.status === 201) {
res.status(201).send({ msg: 'User registration achieved successfully' }) //LINE 17
console.log('User registration achieved successfully')
}
})
} catch(err) {
}
}}
My test code is:
test('should return status 201 and msg', done => {
try {
const fun = handlers.handleRegister()
const res = {
status: jest.fn().mockReturnThis(),
send: function () {
done()
}
}
fun({ body: { email: 'a', password: 'a' } }, res)
expect(res.status).toBeCalledWith(201)
} catch(err) {
done(err)
}
})
The problem is that function handlerRegister line 10 and 11 is correctly executed, but at line 17 I got an error:
/home/anna/Desktop/dev/exampleShop/backend/handlers.js:149
res.status(201).send({
^
TypeError: Cannot read property 'send' of undefined
at auth.register (/home/anna/Desktop/dev/exampleShop/backend/handlers.js:149:26)
at addAccountToDB (/home/anna/Desktop/dev/exampleShop/backend/auth.js:69:7)
at addAccountToDB (/home/anna/Desktop/dev/exampleShop/backend/auth.js:81:3)
at hashPassword (/home/anna/Desktop/dev/exampleShop/backend/auth.js:68:5)
at AsyncWrap.crypto.scrypt (/home/anna/Desktop/dev/exampleShop/backend/auth.js:87:5)
at AsyncWrap.wrap.ondone (internal/crypto/scrypt.js:43:48)
If I use js, not a mock in property res, like:
const res = {
status: function(){return this},
send: function () {
done()
}
}
}
then I don't have this error.
Can someone explain me what is wrong?
There is a scoping issue. res is not defined where you are calling res.send(), because res is being defined inside of the try block.
Either move your expect statement inside of the try like below, or define res in the same scope as your expect statement.
Also you can't call .toBeCalledWith on a function that is not a mocked function. So notice that I have defined res.send to be a mock function, and instead calling done() at the end of your expect statements.
test('should return status 201 and msg', done => {
try {
const fun = handlers.handleRegister()
// res only exists inside of the `try`
const res = {
status: jest.fn().mockReturnThis(),
send: jest.fn() // << is now a mock function
}
fun({ body: { email: 'a', password: 'a' } }, res)
expect(res.status).toBeCalledWith(201)
// here `res.send` is now defined, and you can use `toBeCalledWith`
expect(res.send).toBeCalledWith({ msg: 'User registration achieved successfully' })
done();
} catch(err) {
done(err)
}
})

Async Function working in Express but not NestJs

I initially created a little express server to run a report and file write function.
var ssrs = require('mssql-ssrs');
var fs = require('fs');
const express = require('express')
const app = express()
const port = 3001
app.get('/', (req, res) => {
reportCreation();
res.send('File Created');
})
app.get('/api', (req, res) => {
reportCreation();
res.json({'File Created': true});
})
app.listen(port, () => {
console.log(`Report Api listening at http://localhost:${port}`)
})
The function reportCreation() is an async function which gets a report from a SSRS. This works fine
async function reportCreation() {
var serverUrl = 'http://reportServerName/ReportServer/ReportExecution2005.asmx';
ssrs.setServerUrl(serverUrl);
var reportPath = '/ApplicationPortalReports/TestReportNew';
var fileType = 'word';
var parameters = { ApplicationId: 3, TrainingCardId: 267, PortalPersonId: 52 }
var auth = {
username: 'USERNAME',
password: 'PASSWORD',
domain: 'dmz'
};
try {
var report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
} catch (error) {
console.log(error);
}
console.log(report);
try {
fs.writeFile('ReportApiTest.doc', report, (err) => {
if (!err) console.log('Data written');
});
} catch (error) {
console.log(error);
}
I have been working a lot with NestJs recently and wanted to use the same function but within a NestJs service.
#Injectable()
export class AppService {
async getReport(): Promise<string> {
const serverUrl = 'http://reportServerName/ReportServer/ReportExecution2005.asmx';
ssrs.setServerUrl(serverUrl);
const reportPath = '/ApplicationPortalReports/TestReportNew';
const fileType = 'word';
// var parameters = {appId: 3, ReportInstanceId: 1 }
const parameters = {ApplicationId: 3, TrainingCardId: 267, PortalPersonId: 52 };
const auth = {
username: 'USERNAME',
password: 'PASSWORD',
domain: 'dmz'
};
try {
var report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
} catch (error) {
console.log(error);
}
console.log(report);
// excel = xlsx
// word = doc
// pdf = pdf
try {
fs.writeFile('ReportApiTest.doc', report, (err) => {
if (!err) { console.log('Data written');
return 'File Written Succesfully'}
});
} catch (error) {
console.log(error);
return 'File Write Error'
}
}
}
As you can see the files are almost identical, but when I run it through NestJs I get an error which looks like a problem with the line
var report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
not awaiting. Why does this work with Express and not NestJS? Below is the error from NestJs
buffer.js:219
throw new ERR_INVALID_ARG_TYPE(
^
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string, Buffer, ArrayBuffer,
Array, or Array-like Object. Received type undefined
at Function.from (buffer.js:219:9)
at new Buffer (buffer.js:179:17)
at Object.createType3Message (C:\Projects\SSRS-report-api\ssrs-report-api\node_modules\httpntlm\ntlm.js:172:19)
at sendType3Message (C:\Projects\SSRS-report-api\ssrs-report-api\node_modules\httpntlm\httpntlm.js:77:23)
at Immediate._onImmediate (C:\Projects\SSRS-report-api\ssrs-report-api\node_modules\httpntlm\httpntlm.js:101:4)
within the mssql-ssrs node package the getReportByURL looks like this
async function getReportByUrl(reportPath, fileType, params, auth) {
try {
var config = {
binary: true, // very important
username: auth.userName,
password: auth.password,
workstation: auth.workstation,
domain: auth.domain,
url: soap.getServerUrl()
+ "?" + (testReportPath(reportPath).replace(/\s/g, '+'))
+ "&rs:Command=Render&rs:Format=" + reportFormat(fileType)
+ formatParamsToUrl(params)
};
} catch (err) { report.errorHandler(err) }
return new Promise((resolve, reject) => {
config.url = encodeURI(config.url);
httpntlm.post(config, function (err, res) {
if (res.statusCode === 500) { reject(res) }
if (err || res.statusCode !== 200) { reject(err) }
else { resolve(res.body) }
})
})
}
Here is the app.controller.ts
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): Promise<string> {
return this.appService.getReport();
}
}
This is not an answer for the question. But after I see your code, I can see an error you will face in future if await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth) failed. Actually you see above error because of this.
The way you used the try catch is really bad.
Here's the way I code it.
#Injectable()
export class AppService {
async getReport(): Promise<string> {
const serverUrl = 'http://reportServerName/ReportServer/ReportExecution2005.asmx';
ssrs.setServerUrl(serverUrl);
const reportPath = '/ApplicationPortalReports/TestReportNew';
const fileType = 'word';
// var parameters = {appId: 3, ReportInstanceId: 1 }
const parameters = {ApplicationId: 3, TrainingCardId: 267, PortalPersonId: 52 };
const auth = {
username: 'USERNAME',
password: 'PASSWORD',
domain: 'dmz'
};
const report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
return new Promise(function(resolve, reject) {
fs.writeFile('ReportApiTest.doc', report, , function(err) {
if (err) reject(err);
resolve("File Created");
});
});
}
And in my controller
#POST
async writeFile() {
try {
const res = await this.appService.getReport();
return res;
} catch(err) {
// handle your error
}
}
I had fudged the code in the node_module changing the userName variable to username and had not done the same in the NestJS version. I forgot I had done that so now it is working.

Abstracting Hapi17 routes using async await

I am trying to get my head around the move to await/async in Hapi 17.
Mainly I am trying to work out how to rewrite an abstracted route to one that will work with async/await
The following is an extract from my routes\dogs.js file
const DogController = require('../controllers/dog')
exports.plugin = {
name: 'dogs',
register: async (server, options) => {
server.route({
method: 'GET',
path: '/api/dogs',
handler: DogController.list
})
With the relevant extract from ../controllers/dog
exports.list = (req, h) => {
return Dog.find({}).exec().then((dog) => {
return { dogs: dog }
}).catch((err) => {
return { err: err }
})
}
Which currently returns a list of dogs from a MongoDB database at the moment. I'd like to rewrite the route so that the handler becomes something like
handler: async (request, h) => {
return DogController.list
}
But I'm assuming that the Controller itself is what needs to change.
There is an options property in Route that you can change the behavior of Route in request lifecycle. That means you can create an object and tie it up to your route definition.
Here is your dogs.js route plugin.
exports.plugin = {
async register(server, options) {
const DogController = require('../controllers/dogs');
server.route([
{
method: 'GET',
path: '/dogs',
options: DogController.view
},
]);
},
name: 'dogs-route'
};
And here, your dog controller.
exports.view = {
description: 'list all the good dogs',
handler: async (request, h) => {
return h.view('index', {'title': 'Home Page Title'});
try {
const dogs = await Dog.find({}).exec();
return {dogs};
}
catch(e){
// or something you want
return Boom.badRequest(e.message, e);
}
}
};
Here is a full example that you can check both routes and controllers respectively.

How can I access a function within a router javascript file via node?

I'm handling with an webapplication that is not mine and now I've got to send a hundred e-mails.
Unfortunately, the code is not documented and not so well written, that means i have to go testing it to discover what I am able to do and what I'm not. but I don't know how to access this function that is on the code via node. Is it actually possible to do it? Here's the code:
router.post('/aprovadosemail', miPermiso("3"), (req, res) => {
var templatesDir = path.resolve(__dirname, '..', 'templates');
var emailTemplates = require('email-templates');
// Prepare nodemailer transport object
emailTemplates(templatesDir, function(err, template) {
if (err) {
console.log(err);
} else {
var users = [];
projetoSchema.find({"aprovado":true, "categoria":"Fundamental II (6º ao 9º anos)"}, function (err, docs) {
if (err) throw err;
//console.log(docs);
docs.forEach(function(usr) {
let url = "http://www.movaci.com.b/projetos/confirma/"+usr._id+"/2456";
let url2 = "http://www.movaci.com.br/projetos/confirma/"+usr._id+"/9877";
users.push({'email': usr.email, 'projeto': usr.nomeProjeto, 'url': url, 'url2': url2});
});
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
const transporter = nodemailer.createTransport(smtpTransport({
host: 'smtp.zoho.com',
port: 587,
auth: {
user: "generic#mail.com",
pass: "genericpassword"
},
getSocket: true
}));
var Render = function(locals) {
this.locals = locals;
this.send = function(err, html, text) {
if (err) {
console.log(err);
} else {
transporter.sendMail({
from: 'no-reply4#movaci.com.br',
to: locals.email,
subject: 'MOVACI - Projeto aprovado!',
html: html,
text: text
}, function(err, responseStatus) {
if (err) {
console.log(err);
} else {
console.log(responseStatus.message);
}
});
}
};
this.batch = function(batch) {
batch(this.locals, templatesDir, this.send);
};
};
// Load the template and send the emails
template('rateada', true, function(err, batch) {
for(var user in users) {
var render = new Render(users[user]);
render.batch(batch);
};
});
res.send('ok');
});
};
});
});
Seems like previous dev did not knew email-templates package deeply (at least have not read how it works).
So in fact it has send method, You can create an email object from email-templates and pass necessary defaults, then You call .send method of it by passing dynamical parts - it just simply merge additional params passed in send arguments, sends mail using nodemailer inside of promise which it returns.
If it's interesting for You - read source code of it: https://github.com/niftylettuce/email-templates/blob/master/index.js
I tried to simplify it modular parts using promises.
I've not debugged it, but You may check my solution and fix it as You wish.
Have 2 files (to routing from handler separately, it may have variables that may conflict and etc):
1) methods/users/aprovadosEmail.js:
const
Email = require('email-templates'),
const
emailTemplatesDir = path.resolve(__dirname + '/../../templates'),
smtpTransportConfig = {
host: 'smtp.zoho.com',
port: 587,
secure: false,
auth: {
user: "no-reply4#movaci.com.br",
pass: "some-password-here"
}
},
createEmail = (template, subject) => {
return new Email({
views: {
root: emailTemplatesDir,
},
transport: smtpTransportConfig,
template,
message: {
from: 'no-reply4#movaci.com.br',
subject
}
});
},
getApprovedUsers = () => {
return new Promise((resolve, reject) => {
const criteria = {
aprovado: true,
categoria:"Fundamental II (6º ao 9º anos)"
};
projetoSchema.find(
criteria,
(error, docs) => {
if(error) return reject(error);
const users = docs.map(doc => {
return {
email: doc.email,
projeto: doc.nomeProjeto,
url: "http://www.movaci.com.b/projetos/confirma/"+doc._id+"/2456",
url2: "http://www.movaci.com.br/projetos/confirma/"+doc._id+"/9877"
};
});
resolve(users);
});
});
},
sendMailToUser = (mail, user) => {
return mail.send({
message: {
to: user.email
},
locals: user
});
},
broadcastMailToUsers = (mail, users) => {
return Promise
.all(
users
.map(user => sendMailToUser(mail, user))
);
};
module.exports = (req, res) => {
const mail = createEmail('rateada', 'MOVACI - Projeto aprovado!'); // mail object
getApprovedUsers()
.then(users => broadcastMailToUsers(mail, users))
.then(result => {
console.log('Result of broadcast:', result);
res.send('ok');
})
.catch(error => {
res.status(500).send(error);
});
};
2) current routes file where routing part that uses module file:
router.post(
'/aprovadosemail',
miPermiso("3"),
require(__dirname+'/../methods/users/aprovadosEmail')
);

Bubble Up Error From Promise Functions

I am trying to get used to using Promises.
However, I am struggling to get an error throw in a sub-function to bubble up to the main function.
I use Senecajs for all my modules, like so:
// resend-invite.js
module.exports = function(options) {
const Promise = require('bluebird'),
seneca = this,
act = Promise.promisify(seneca.act, {context: seneca});
this.add({role:'project-actions', cmd: 'resend-invite'}, function( msg, done ) {
return findProject()
.then(function(project) {
if ( !project.userHasAdminAccess( msg.user_id ) ) {
throw Error( 'User does not have admin access.' );
}
// makes it here...
return act( { role: "user-repository", cmd: "get-referral-user", user: { email: msg.email } } );
})
.then(function(referral_user) {
// doesn't make it here :(
var referral_name = referral_user.first_name + " " + referral_user.last_name;
return anotherFunction();
})
.then(function(mail_info) {
done(null, mail_info);
})
.catch(function(err) {
// doesn't make it here :(
done(err);
})
});
}
And like this:
// get-referral-user.js
module.exports = function(options) {
const Promise = require('bluebird'),
seneca = this,
User = require('app/models/user');
this.add({role:'user-repository', cmd: 'get-referral-user'}, function( msg, done ) {
return User.findOne(msg.user).exec()
.then(function(user) {
var referral_user_id;
if (user == null) {
throw Error("User wasn't found.");
}
referral_user_id = user.referral_user_id;
return User.findById(referral_user_id).exec()
})
.then(function(user) {
done(null, user.toObject());
})
.catch(function(err) {
// gets to this err handler
done(err);
})
});
}
However, when there is an error in get-referral-user.js, it is caught by the get-referral-user.js .catch block, but the error is not caught by thr resend-invite.js catch block.
How do I get the resend-invite.js code to detect that a sub-function (act( { role: "user-repository", cmd: "get-referral-user", user: { email: msg.email } } ) in this case) has an error and needs to be handled.
Here are the parts of the error that is produced in my console that could be helpful:
Seneca Fatal Error
Message: seneca: Action cmd:get-referral-user,role:user-repository failed: User wasn't found..
..
ALL ERRORS FATAL: action called with argument fatal$:true (probably a plugin init error, or using a plugin seneca instance)
Any help is greatly appreciated. Thank you.

Categories

Resources