How can this function be tested with vows? - node.js

How can the following function, intended to add routes to express.js app based on an object hierarchy, be tested using vows.js cleanly without breaking vows' separation of the topic and the vow?
var addRoutes = function(routeObject, app, path) {
var httpVerbs = ['get', 'post', 'put', 'delete'];
path = path || '';
for(var property in routeObject){
var routesAdded = false;
for (var verbIndex in httpVerbs) {
var verb = httpVerbs[verbIndex];
var completePath, handler;
if (property === verb) {
if (typeof(routeObject[verb]) === 'function') {
handler = routeObject[verb];
completePath = path;
} else {
handler = routeObject[verb].handler;
completePath = path + (routeObject[verb].params || '');
}
app[verb](completePath, handler);
routesAdded = true;
}
}
if (!routesAdded) {
addRoutes(routeObject[property], app, path + '/' + property);
}
}
};
The test should confirm that functions corresponding to http verbs are called on the app object. e.g. if routeObject is:
{
tracker: {
message: {
get: {
handler: function (req, res) {
res.send("respond with a resource");
}
}
}
}
}
then
app.get('/tracker/message', function (req, res) {
res.send("respond with a resource");
});
should be called.

Something like this should work:
var assert = require('assert'),
request = require('request');
var verbs = ['get', 'post', 'put', 'delete'],
app = require('your/app.js'),
port = 8080;
//
// Your route would look something like this
//
var example = {
tracker: {
message: {
get: {
status: 200,
body: 'respond with a resource'
}
}
}
};
function testRoutes(suite, route, app, path) {
path = path || '/';
Object.keys(route).forEach(function (key) {
if (~verbs.indexOf(key)) {
return testRoutes(suite, route[key], app, [path, key].join('/'));
}
var test = [key, path].join(' ');
suite[test] = {
topic: function () {
request({
method: key,
uri: 'http://localhost' + port + path
}, this.callback);
},
'should respond correctly': function (err, res, body) {
assert.isNull(err);
assert.equal(res.statusCode, route[key].status);
assert.equal(body, route[key].body);
}
}
});
return suite;
}
var suite = vows.describe('api');
testRoutes(suite, example, app);
suite.export(module);

Related

Synchronous api calls in Node.js

I've a cronjob that runs every 10 secs. It requests for machines for a single client and does computation based on the response and then has to update or create documents with those computations in a for loop. But, the api calls after '***' in the code don't happen until the for loop has executed and the data sent to the api calls is that of the last machine which is wrong. I want to solve this by this way or some other way possible. My code looks like this:
// index.js
const cron = require("node-cron");
const express = require("express");
const fs = require("fs");
const request = require("request");
app = express();
var clientId = 'ABCD';
var apiUrl = 'http://example.com:3001/';
var getMachines = apiUrl + 'getMachines/',
updateMachine = apiUrl + 'updateMachine/',
getControlRoomStatus = apiUrl + 'getControlRoomStatus/',
registerControlRoomStatus = apiUrl + 'registerControlRoomStatus/',
updateControlRoomStatus = apiUrl + 'updateControlRoomStatus/';
cron.schedule("*/10 * * * * *", function() {
APICall(getMachines, { 'clientId' : clientId }, 'POST', function(err, machines) {
if (err) {
console.log(err);
} else {
console.log('--------------------------------------------------------------------------------------------------');
var allMachines = machines;
var currentDateTime = IST();
for (var i = 0; i < 2; i++) {
var lastCycleTime = new Date(allMachines[i]['lastCycleTime']);
var lastHeartbeat = new Date(allMachines[i]['lastHeartbeat']);
var machineData;
var controlRoomData;
var machineId = {
'machineId' : allMachines[i]['machineId']
};
console.log(machineId);
if (allMachines[i]['downtimeStatus'] == '0') {
if ((currentDateTime - lastCycleTime)>300000) {
if ((currentDateTime - lastHeartbeat)>300000) {
console.log(allMachines[i]['machineId'] ,' No Internet');
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': 'No Internet/Power'
};
} else {
console.log(allMachines[i]['machineId'] ,' No button pressed');
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': 'No Button Pressed'
};
}
machineData = {
'status' : 'Inactive'
};
} else {
console.log(allMachines[i]['machineId'] ,' Active');
machineData = {
'status' : 'Active'
};
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Active',
'type': 'N.A'
};
}
} else {
if ((currentDateTime - lastHeartbeat)>300000) {
console.log(allMachines[i]['machineId'] ,' button pressed ',' No Internet');
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': 'No Internet/Power'
};
} else {
var downtimeLength = allMachines[i]['downtimeData'].length - 1;
console.log(allMachines[i]['machineId'] ,' button pressed ',allMachines[i]['downtimeData'][downtimeLength]['downtimeType']);
controlRoomData = {
'clientId': clientId,
'lastTimeStamp': allMachines[i]['lastCycleTime'],
'status': 'Inactive',
'type': allMachines[i]['downtimeData'][downtimeLength]['downtimeType']
};
}
machineData = {
'status' : 'Inactive'
};
}
***
APICall(getControlRoomStatus, machineId, 'POST', function(err, controlRoom) {
if (err) {
console.log(err);
} else {
console.log(machineId,controlRoomData);
if (controlRoom == null ) {
APICall(registerControlRoomStatus, controlRoomData, 'POST', function(err, body) {
if (err) {
console.log(err);
} else {
// console.log(body);
}
});
} else {
var updateControlRooomUrl = (updateControlRoomStatus+''+controlRoom['_id']+'');
// console.log(updateControlRooomUrl);
APICall(updateControlRooomUrl, controlRoomData, 'PUT', function(err, body) {
if (err) {
console.log(err);
} else {
// console.log(body);
}
});
}
}
});
var updateMachineUrl = (updateMachine+''+allMachines[i]['_id']+'');
// console.log(updateMachineUrl);
APICall(updateMachineUrl, machineData, 'PUT', function(err, body) {
if (err) {
console.log(err);
} else {
console.log(i,machineId);
// console.log(body);
}
});
}
}
});
});
function APICall(url, requestData, method, callback) {
request({
url: url,
form: requestData,
method: method
}, function (error, response, body) {
if (error || response.statusCode !== 200) {
return callback(error || {statusCode: response.statusCode});
}
callback(null, JSON.parse(body));
});
}
function IST(){
var dateUTC = new Date();
var dateUTC = dateUTC.getTime();
var dateIST = new Date(dateUTC);
dateIST.setHours(dateIST.getHours() + 5);
dateIST.setMinutes(dateIST.getMinutes() + 30);
return dateIST;
}
app.listen(3128);
Thank you in advance.
I used a different method to do things and now it's working just as it's supposed to. I used 'async' and replaced the for loop with the following:
var async = require('async');
...
async.map(allMachines , function(machine, callback) {
...
});
...
You can try the following package:
sync-request
You can find it here on NPM.
Here is an example how to use it (from the docs):
var request = require('sync-request');
var res = request('GET', 'http://example.com');
console.log(res.getBody());
As stated in the documentation, don't use it in production code, since this will terribly block your server and it will slow down considerably (if you are running a HTTP server which you are using express).
If you have asynchronous code and you want to execute some code after the asynchronous you also can use:
Observables (not native need to use a package, RxJS for example)
Promises (native ES6 JS)

Editing the context variables in Watson Assistant for a specific dialogue node using Node js

I have a code that links Watson conversation to facebook messenger.
Now, I want to implement the following conversation -
User: I need a vehicle
Bot: Tell the location where the vehicle is needed
User: Santa Monica Pier
Bot: ****identifies the location as a context variable ($location) from Watson Assistant*
*Finds out the ETA from node app and sends it to Watson Assistant as a context variable ($ETA) ****
Bot: Your vehicle will arrive in $ETA minutes.
My code is -
var express = require('express');
var request = require('request');
var bodyParser = require('body-parser');
var watson = require('watson-developer-cloud');
var app = express();
var contexid = "";
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
var conversation_id = "";
var w_conversation = watson.conversation({
url: 'https://gateway.watsonplatform.net/conversation/api',
username: process.env.CONVERSATION_USERNAME || 'username',
password: process.env.CONVERSATION_PASSWORD || 'pass',
version: 'v1',
version_date: '2016-07-11'
});
var workspace = process.env.WORKSPACE_ID || 'id';
app.get('/webhook/', function (req, res) {
if (req.query['hub.verify_token'] === 'fb_token') {
res.send(req.query['hub.challenge']);
}
res.send('Error : no token.');
});
app.post('/webhook/', function (req, res) {
var text = null;
messaging_events = req.body.entry[0].messaging;
for (i = 0; i < messaging_events.length; i++) {
event = req.body.entry[0].messaging[i];
sender = event.sender.id;
if (event.message && event.message.text) {
text = event.message.text;
}else if (event.postback && !text) {
text = event.postback.payload;
}else{
break;
}
var params = {
input: text,
// context: {"conversation_id": conversation_id}
context:contexid
}
var payload = {
workspace_id: "id"
};
if (params) {
if (params.input) {
params.input = params.input.replace("\n","");
payload.input = { "text": params.input };
}
if (params.context) {
payload.context = params.context;
}
}
callWatson(payload, sender);
}
res.sendStatus(200);
});
function callWatson(payload, sender) {
w_conversation.message(payload, function (err, convResults) {
console.log(convResults);
contexid = convResults.context;
if (err) {
return responseToRequest.send("Erro.");
}
if(convResults.context != null)
conversation_id = convResults.context.conversation_id;
if(convResults != null && convResults.output != null){
var i = 0;
while(i < convResults.output.text.length){
sendMessage(sender, convResults.output.text[i++]);
}
}
});
}
function sendMessage(sender, text_) {
text_ = text_.substring(0, 319);
messageData = { text: text_ };
request({
url: 'https://graph.facebook.com/v2.6/me/messages',
qs: { access_token: token },
method: 'POST',
json: {
recipient: { id: sender },
message: messageData,
}
}, function (error, response, body) {
if (error) {
console.log('Error sending message: ', error);
} else if (response.body.error) {
console.log('Error: ', response.body.error);
}
});
};
var token = "token";
var host = (process.env.VCAP_APP_HOST || 'localhost');
var port = (process.env.VCAP_APP_PORT || 3000);
app.listen(port, host);
How do I identify the location as a context variable ($location) from Watson Assistant and send ETA from node app to Watson Assistant as a context variable?
I was thinking of using action to make a programmatic call in my Watson dialogue node to do it but was unable to implement it.
convResults will contain the full context object, including the context variables that your dialog has set.

Why is this not working, And how do you debug code in nodejs?

First I'm trying to learn nodejs, and for that I am writing like a router. As you have in express.
This is my code:
function sirus() {
var util = utilFuncs(),
paths = {};
this.router = util.handleReq;
this.con = {
get: function (path, fn, options) {
options = (options || {}).method = "GET";
util.makeRequest(path, fn, options);
},
post: function (path, fn, options) {
options = (options || {}).method = "POST";
util.makeRequest(path, fn, options);
}
};
this.req = {
get: function (path, fn) {
util.addPath("GET", path, fn);
},
post: function (path, fn) {
util.addPath("POST", path, fn);
}
};
function utilFuncs() {
function handleReq(req, res) {
var url = parsePath(req.url);
var path = paths[req.method + url];
// console.log(req.url, url +' requested');
if (typeof path != "function") {
res.writeHead(404);
} else {
path(req, res);
}
res.end();
}
function addPath(type, path, callback) {
// add path as a key of object for easier look up, root=/, /a=/a, /a/=/a, /a?=/a, /a/?..=/a so there are no duplicates path=parsePath(path);
paths[type + path] = callback;
}
function parsePath(path) {
path = url.parse(path).pathname;
if ((/[^\/]+\/(?=$)/igm).test(path)) path = path.substring(0, path.length - 1);
return path;
}
function makeRequest(path, fn, options) {
var urls = url.parse(path);
var d = {
host: null,
hostname: null,
method: "GET",
path: '/',
port: 80,
headers: {},
auth: null,
agent: false,
keepAlive: false
};
for (var k in options) d[k] = options[k];
d.host = urls.host;
d.hostname = urls.hostname;
d.path = urls.pathname;
d.headers['Content-Type'] = 'application/x-www-form-urlencoded';
d.headers['Content-Length'] = ((d || {}).body || {}).length || '';
var req = http.request(options, function (res) {
// console.log('STATUS: ' + res.statusCode);
// console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function (chunk) {
data += chunk;
});
res.on('response', function () {
fn(res);
});
});
req.on('error', function (e) {
console.log('problem with request: ' + e.message);
});
req.write(d.body);
req.end();
}
return {
makeRequest: makeRequest,
handleReq: handleReq,
addPath: addPath
};
}
}
and i use it like this:
var http = require('http'),
url = require('url'),
app = new sirus();
http.createServer(function (req, res) {
app.router(req, res);
}).listen(80, '127.0.0.1');
app.req.get('/', function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
var link = "https://www.reddit.com/";
res.write('Click here');
});
app.con.get('http://www.google.com/', function (res) {
console.log('from req: ' + res);
});
The error i get is The First argument must be string or buffer
Receiving part works correctly, but when i started to add make request part something isn't right.
Also since I'm new to JS and Nodejs. I'd like general advice or anything that catch your eye something that i can be improved about this code. Since I'm note sure i am doing things the best way.
On line 85
req.write(d.body);
there is no member named "body" in the "d" object.
1 - Why is this not working - you get exactly error that tells you why it is not working. If you check more details about error it shows the file and which line error happened at. Perhaps the arguments you pass to function call are not what Express is expecting.
2 - How to debug - look at node.js package called nodev.

Export HTTP request as module function Node.js

I've tried (with success) to do an http request. I have some REST Api's, like graph.facebook.com as target. The functionality is the same. When i make an HTTP request with node.js as a simple program i can do it.
Really i want to make a little module, and i have this code:
// file: facebook.js
var http = require('http');
var Facebook = (function() {
function Facebook(access_token) {
this.access_token = access_token;
}
Facebook.prototype.getObject = function(id) {
var options;
this.id = id;
options = {
host: 'graph.facebook.com',
port: 80,
path: '/' + id + '?access_token=' + this.access_token
};
return http.request(options, function(response) {
response.on('data', function(d) {
return JSON.parse(d);
});
request.end();
return request.on('error', function(err) {
return {
error: 'An error ocurred.'
};
});
});
};
return Facebook;
})();
module.exports = Facebook;
After, when i write a program i can do this:
var facebook = require('./facebook.js');
var fb = facebook('my_Access_token')
// Here's the problem:
var response = fb.getObject('My_facebook_profile_ID')
I get a response like
{ domain: null,
_events:
{ response: { [Function: g] listener: [Function] },
socket: { [Function: g] listener: [Function] } },
....
When i must have something like
{
"id": "MyIDNumer",
"first_name": "Orlando",
"gender": "male",
"last_name": "Sanchez",
"link": "https://www.facebook.com/MyFacebookName",
"locale": "es_LA",
"name": "Orlando S\u00e1nchez",
"username": "MyUsername"
}
What can i do?
The first thing you should do is rewrite the module to not use the same function name twice ('Facebook').
Secondly, there's no need for the closure, just export the constructor.
Thirdly, you are trying to return a value from an asynchronous callback. This particular issue is common with people coming from the synchronous world.
Assuming you wanted to do a GET request, here's your module refactored after the above and other things are fixed:
// file: facebook.js
var http = require('http');
function Facebook(access_token) {
if (!(this instanceof Facebook))
return new Facebook(access_token);
this.access_token = access_token;
}
Facebook.prototype.getObject = function(id, cb) {
var options;
this.id = id;
options = {
host: 'graph.facebook.com',
port: 80,
path: '/' + id + '?access_token=' + this.access_token
};
http.get(options, function(res) {
var buf = '',
hadError = false;
res.on('data', function(d) {
buf += d;
}).on('error', function(err) {
hadError = true;
cb(err);
}).on('end', function() {
if (hadError)
return;
var val;
try {
val = JSON.parse(buf);
} catch (err) {
return cb(err);
}
cb(null, val);
});
});
};
module.exports = Facebook;
Then use it like so:
var facebook = require('./facebook.js');
var fb = facebook('my_Access_token');
fb.getObject('My_facebook_profile_ID', function(err, response) {
if (err) {
// include better error handling here
return console.log(err);
}
// use response here
console.dir(response);
});

Node.js, Proxy HTTPS traffic without re-signing

I'm building a proxy using Node.js to be run on my local machine that will log all domains accessed. Kind of the same way Fiddler works, but my program is more simple, I don't need to look at the data or decrypt anything.
I got this working fine for HTTP but for HTTPS it resigns the traffic with the self-signed certificate provided. This results in that the browser displays a warning. The same thing doesn't happen in fiddler unless you choose to decrypt HTTPS traffic.
So my question is: How do I proxy HTTPS traffic using Node.js so that it is completely transparent for the user?
This is the code I'm using right now, using the Node.js http-proxy. Based on the Github project, proxy-mirror.
var httpProxy = require('http-proxy'),
https = require('https'),
connect = require('connect'),
logger = connect.logger('dev'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
Iconv = require('iconv').Iconv,
convertBuffer = require('./convertBuffer'),
fs = require('fs'),
net = require('net'),
url = require('url'),
path = require('path'),
certDir = path.join(__dirname, '/../cert'),
httpsOpts = {
key: fs.readFileSync(path.join(certDir, 'proxy-mirror.key'), 'utf8'),
cert: fs.readFileSync(path.join(certDir, 'proxy-mirror.crt'), 'utf8')
};
var Session = function (sessionId, req, res) {
EventEmitter.call(this);
var that = this;
this.id = req._uniqueSessionId = res._uniqueSessionId = sessionId;
this.request = req;
this.request.body = {asString: null, asBase64: null};
this.response = res;
this.response.body = {asString: null, asBase64: null};
this.state = 'start';
var hookInResponseWrite = function () {
var response = that.response;
var _write = response.write;
var output = [];
response.write = function (chunk, encoding) {
output.push(chunk);
_write.apply(response, arguments);
};
return output;
}, hookInRequestData = function () {
var request = that.request,
output = [];
request.on('data', function (chunk, encoding) {
output.push(chunk);
});
request.on('end', function () {
var buffersConcatenated = Buffer.concat(output);
request.body.asString = buffersConcatenated.toString();
that.emit('request.end', that);
});
return output;
};
this.response.bodyBuffers = hookInResponseWrite();
this.request.bodyBuffers = hookInRequestData();
this.ended = function () {
var buffersConcatenated = Buffer.concat(this.response.bodyBuffers);
this.response.body.asString = convertBuffer.convertEncodingContentType(buffersConcatenated,this.response.getHeader('content-type') || '');
this.response.body.asBase64 = buffersConcatenated.toString('base64');
this.removeAllListeners();
};
};
util.inherits(Session, EventEmitter);
Session.extractSessionId = function (req) {
return req._uniqueSessionId;
};
var SessionStorage = function () {
var sessionHash = {},
sessionIdCounter = 0,
nextId = function () {
return sessionIdCounter += 1;
};
this.startSession = function (req, res) {
var sessionId = nextId(), session;
sessionHash[sessionId] = session = new Session(sessionId, req, res);
return session;
};
this.popSession = function (req) {
var sessionId = Session.extractSessionId(req),
session = sessionHash[sessionId];
delete sessionHash[sessionId];
return session;
};
};
var ProxyServer = function ProxyServer() {
EventEmitter.call(this);
var proxyPort = 8888,
secureServerPort = 8887,
parseHostHeader = function (headersHost, defaultPort) {
var hostAndPort = headersHost.split(':'),
targetHost = hostAndPort[0],
targetPort = parseInt(hostAndPort[1]) || defaultPort;
return {hostname: targetHost, port: targetPort, host: headersHost};
},
sessionStorage = new SessionStorage(),
adjustRequestUrl = function(req){
if (requestToProxyMirrorWebApp(req)) {
req.url = req.url.replace(/http:\/\/localhost:8889\//, '/');
}
req.url = url.parse(req.url).path;
},
proxyServer = httpProxy.createServer({
changeOrigin: true,
enable: {
xforward: false
}
}, function (req, res, proxy) {
var parsedHostHeader = parseHostHeader(req.headers['host'], 80),
targetHost = parsedHostHeader.hostname,
targetPort = parsedHostHeader.port;
req.originalUrl = req.url;
adjustRequestUrl(req);
logger(req, res, function () {
proxy.proxyRequest(req, res, {
host: targetHost,
port: targetPort
});
})
}),
proxy = proxyServer.proxy,
secureServer = https.createServer(httpsOpts, function (req, res) {
var parsedHostHeader = parseHostHeader(req.headers.host, 443);
// console.log('secure handler ', req.headers);
req.originalUrl = req.url;
if(!req.originalUrl.match(/https/)){
req.originalUrl = 'https://' + parsedHostHeader.host + req.url;
}
adjustRequestUrl(req);
logger(req, res, function () {
proxy.proxyRequest(req, res, {
host: parsedHostHeader.hostname,
port: parsedHostHeader.port,
changeOrigin: true,
target: {
https: true
}
});
});
}),
listening = false,
requestToProxyMirrorWebApp = function (req) {
var matcher = /(proxy-mirror:8889)|(proxy-mirror:35729)/;
return req.url.match(matcher) || (req.originalUrl && req.originalUrl.match(matcher));
};
[secureServer,proxyServer].forEach(function(server){
server.on('upgrade', function (req, socket, head) {
// console.log('upgrade', req.url);
proxy.proxyWebSocketRequest(req, socket, head);
});
});
proxyServer.addListener('connect', function (request, socketRequest, bodyhead) {
//TODO: trying fixing web socket connections to proxy - other web socket connections won't work :(
// console.log('conenct', request.method, request.url, bodyhead);
var targetPort = secureServerPort,
parsedHostHeader = parseHostHeader(request.headers.host);
if(requestToProxyMirrorWebApp(request)){
targetPort = parsedHostHeader.port;
}
var srvSocket = net.connect(targetPort, 'localhost', function () {
socketRequest.write('HTTP/1.1 200 Connection Established\r\n\r\n');
srvSocket.write(bodyhead);
srvSocket.pipe(socketRequest);
socketRequest.pipe(srvSocket);
});
});
this.emitSessionRequestEnd = function (session) {
this.emit('session.request.end', session);
};
this.startSession = function (req, res) {
if (requestToProxyMirrorWebApp(req)) {
return;
}
var session = sessionStorage.startSession(req, res);
this.emit('session.request.start', session);
session.on('request.end', this.emitSessionRequestEnd.bind(this));
};
this.endSession = function (req, res) {
if (requestToProxyMirrorWebApp(req)) {
return;
}
var session = sessionStorage.popSession(req);
if (session) {
session.ended();
this.emit('session.response.end', session);
}
};
this.start = function (done) {
done = done || function () {
};
proxy.on('start', this.startSession.bind(this));
proxy.on('end', this.endSession.bind(this));
proxyServer.listen(proxyPort, function () {
secureServer.listen(secureServerPort, function () {
listening = true;
done();
});
});
};
this.stop = function (done) {
done = done || function () {
};
proxy.removeAllListeners('start');
proxy.removeAllListeners('end');
if (listening) {
secureServer.close(function () {
proxyServer.close(function () {
listening = false;
done();
});
});
}
};
};
util.inherits(ProxyServer, EventEmitter);
module.exports = ProxyServer;

Resources