I am VERY new to Promises in javascript, so this question is to help me figure out why I am getting an error (in a weird order) using a promise. On top of this, I am busy using the ms-sql repo, sinonjs and restify for the first time, so that doesn't help either
This question is related to one I asked earlier here
In the question above, I needed to stub out the SQL DB which I successfully did with the help of #robertklep. However, as a sanity check, I wanted to check that the endpoints were still returning the data I expect as before, so I undid the stubbing. Now I am getting the following error and I have no idea why:
[Error: Can't set headers after they are sent.]
Test:
'use strict';
var expect = require('chai').expect,
request = require('supertest'),
chance = require('chance').Chance(),
server = require('../server'),
sinon = require('sinon'),
select = require('../../helpers/data_access/select'),
read_file = require('../../helpers/read_file');
describe("/account_types", function () {
// before(function (done) {
// sinon
// .stub(select, "query_list")
// .returns([{id: "test"}]);
//
// sinon
// .stub(select, "query_single")
// .returns({id: "test"});
//
// sinon
// .stub(read_file, "get_file_contents")
// .returns("Some content to get excited about");
//
// done();
// });
//
// after(function (done) {
// select
// .query_list
// .restore();
// select
// .query_single
// .restore();
// read_file
// .get_file_contents
// .restore();
//
// done();
// });
it('GET 200 List', function (done) {
request(server.baseURL)
.get('/api/v1/account_types')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.not.be.null;
expect(res.body).to.not.be.undefined;
expect(res.body).to.be.an('Array');
expect(res.body.length).to.be.above(0);
//expect(select.query_list).to.have.been.calledOnce;
return done();
});
});
it('GET 200 Single', function (done) {
//var param = chance.random();
request(server.baseURL)
.get('/api/v1/account_type/' + 1)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.not.be.null;
expect(res.body).to.not.be.undefined;
expect(res.body).to.be.an('Object');
expect(res.body.toString().length).to.be.above(0);
return done();
});
});
it('POST 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.post('/api/v1/account_type')
.set('Accept', 'application/json')
.send({id: param})
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('post account_type : ' + param.toString());
return done();
});
});
it('PUT 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.put('/api/v1/account_type')
.set('Accept', 'application/json')
.send({id: param})
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('put account_type : ' + param.toString());
return done();
});
});
it('DELETE 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.delete('/api/v1/account_type/' + param)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('delete account_type : ' + param.toString());
return done();
});
});
});
Endpoints:
var select = require('../helpers/data_access/select'),
read_file = require('../helpers/read_file'),
format = require('string-format');
const db_config_name = 'db.goaml';
module.exports = function (server) {
server.get('/api/v1/account_types', function (req, res, next) {
var query = read_file.get_file_contents('path to query');
select.query_list(db_config_name, query, function (err, records) {
console.log('test 1');
if (err != null) {
return next(err);
}
res.send(records);
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
server.get('/api/v1/account_type/:id', function (req, res, next) {
var query =
format(read_file.get_file_contents('path to query'), req.params.id);
select.query_single(db_config_name, query, function (err, records) {
console.log('test 2');
if (err != null) {
return next(err);
}
res.send(records[0]);
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
server.post('/api/v1/account_type', function (req, res, next) {
res.send({'result': 'post account_type : ' + req.body.id});
return next();
});
server.put('/api/v1/account_type', function (req, res, next) {
res.send({'result': 'put account_type : ' + req.body.id});
return next();
});
server.del('/api/v1/account_type/:id', function (req, res, next) {
res.send({'result': 'delete account_type : ' + req.params.id});
return next();
});
};
select.js:
var sql = require('mssql'),
config = require('./configs/config');
module.exports = {
query_list: function (config_name, sql_query, callback) {
return query(config_name, sql_query, true, callback);
},
query_single: function (config_name, sql_query, callback) {
return query(config_name, sql_query, false, callback);
}
};
function query(config_name, sql_query, isList, callback) {
var db_config = config.get(config_name),
connection = new sql.Connection(db_config);
connection.connect(function () {
new sql.Request(connection)
.query(sql_query)
.then(function (records) {
console.log('test 3');
callback(null, isList ? records : records[0]);
connection.close();
})
.catch(function (err) {
console.log('test 4');
console.log(err);
callback(err, null);
});
});
// EDIT for answer:
// This catch is not allowed. IE the connection isn't a promise. Thus
// when the code responded with something valid, it reached this part
// after that and realised this is an illegal statement. As a result
// it throws an error after the res.send was already hit and showing the
// initial error I reported.
// .catch(function (err) {
// console.log('test 5');
// callback(err, null);
// });
}
When I run the gulp task to test the above mentioned code, I get the following output:
C:\Code\JS\general_admin_service>gulp test
[16:12:47] Using gulpfile C:\Code\JS\general_admin_service\gulpfile.js
[16:12:47] Starting 'test'...
[16:12:47] Finished 'test' after 62 ms
/account_types
1) GET 200 List
2) GET 200 Single
test 3
test 1
test 4
[Error: Can't set headers after they are sent.]
test 1
√ POST 200 Single
test 3
test 2
test 4
[Error: Can't remove headers after they are sent.]
test 2
√ PUT 200 Single
√ DELETE 200 Single
3 passing (400ms)
2 failing
1) /account_types GET 200 List:
Error: expected 200 "OK", got 500 "Internal Server Error"
at Test._assertStatus (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:247:11)
at Test.assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:148:18)
at assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:127:12)
at C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:124:5
at Test.Request.callback (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:831:3)
at Stream.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:1049:12)
at Unzip.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\utils.js:108:12)
2) /account_types GET 200 Single:
Error: expected 200 "OK", got 500 "Internal Server Error"
at Test._assertStatus (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:247:11)
at Test.assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:148:18)
at assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:127:12)
at C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:124:5
at Test.Request.callback (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:831:3)
at Stream.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:1049:12)
at Unzip.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\utils.js:108:12)
events.js:142
throw er; // Unhandled 'error' event
^
Error: 2 tests failed.
What baffles me is that if I look at the output of the console.log's, it is clear that the code enters the "successful" then block of the sql.request and calls the callback. I can confirm that if I add a console.log to check the value the records param, there is actually data coming back in it.
At this point the res.send get's hit, but then an error is caught in the catch block of the sql.query and at this point is where the confusion hits:
If there was a successful return, why is the catch being hit?
Which part of the code is responding to the test. I.E. why is the headers being changed after it was set? (Does the next(err) perhaps have something to do with it?)
As mentioned before, this question has a lot to do with my previous one. I had to make changes to the code so as to satisfy my unit tests. However, I feel it necessary that I do a sanity check and make sure data is still being returned as expected and at this point, I think the Promise framework which ms-sql uses gets in the way of simply returning the values to the test. (I am not disputing the value of the Promise framework).
I hope someone has an idea what I am doing wrong.
EDIT:
So I am a bit confused as to what is going on. With the code as it is, if I uncomment the before and after parts of the tests, the tests fails and it looks like it has something to do with the callbacks.
If I remove the callbacks completely and have a return in the then of the sql.Request and throw err in the catches then my tests passes.
However, this setup will not return a value if I do straight up tests with no stubbing.
This is happening because you are executing a return next(); before the callback from the SQL Server has completed. The reason that's a problem is because, while it's asynchronous in nature, you're performing a synchronous set of operations overall.
What's happening is the res.send(records); is failing because next() has already been called and the server has already returned a 200.
Finally, you don't need the return next(); after the res.send(records); because res.send(records); ends the process and returns the response.
Related
For put request:
router.put('/:id', controller.update);
My update method look like this:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.status(404).send('Not Found'); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.status(200).json(thing);
});
});
};
Making request:
$http.put('/api/things/'+ thing._id, updatedThingObject)
.success(function(update){
console.log("update", update)
})
.error(function(err){
console.log("err", err)
})
It gives connection error on passing the object while making the request in angular.
The error looks like this:
PUT http://localhost:9000/api/things/56c8325b9a0ee7d00d266495
net::ERR_CONNECTION_REFUSED(anonymous function) # angular.js:11442sendReq #
If I take off the updated object, it makes the request just fine but ofcourse nothing gets updated in
that case. What might be wrong here,please?
I figured.
The reason for the functions not being called is that I have a function that is being called repetitively in Node .
var autoCreate = function(){
console.log("THING CREATED AUTOMATICALLY")
var randomNumb=0;
clearTimeout(randomNumb);
randomNumb = (Math.random()* (10-5) + 5).toFixed(0);
console.log("random number", randomNumb)
var randomThing =randomstring({
length: randomNumb,
numeric: false,
letters: true,
special: false
});
console.log("ranfom thing", randomThing)
Thing.create({
name: randomThing,
readByUser: false
}, function(err, thing) {
console.log("THING IS", thing)
//setTimeout(autoCreate, randomNumb * 1000);
});
}
setTimeout(autoCreate, 10*1000);
Since this is running when post/put request is made, I get connection error. How do I handle this to be able to have this function running and be able to make put/post requests as well?
This is for a project for college. I am having difficulty understanding how to handle express responses with angular. My partner handled most of the back end and I took care of most of the front end, as to how to get information from express and use it on the front end. Our routing is below, if it will help.
// set variables for environment
var express = require('express');
var app = express();
var path = require('path');
var bodyParser = require('body-parser');
//tell express to use the bodyParser middleware
app.use(bodyParser());
//start the mysql interface
var mysql = require('mysql');
var mysql = require('mysql');
var connectionPool = mysql.createPool({
host : 'localhost',
user : '<user>',
password : '<password>',
database : '<table>'
});
// connection.connect();
// connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
// if (err) throw err;
// console.log('The solution is: ', rows[0].solution);
// });
// connection.end();
// Set server port
app.listen(80);
console.log('server is running at 127.0.0.1:80');
// views as directory for all template files
app.set('views', path.join(__dirname, 'views'));
// instruct express to server up static assets
app.use(express.static('public'));
// set routes
app.get('/', function(req, res) {
res.sendFile(__dirname + '/views/index.html');
});
app.get('/:file', function(req, res) {
res.sendFile(__dirname + '/views/' + req.params.file);
});
app.get('/req/:itemname', function(req,res)
{
connectionPool.getConnection(function(err, connection)
{
if(err)
{
console.log('connection error: \n\n\n');
console.log(err);
res.statusCode = 503;
res.send({
result: 'error',
err: err.code
});
}
else
{
var query = 'SELECT * FROM Product WHERE name LIKE \'%' + req.params.itemname +'%\' ORDER BY ProductID asc';
console.log(query);
connection.query(query, req.params.id, function(err, rows, fields)
{
if(err)
{
console.log(err);
res.statusCode = 500;
res.send({
result: 'error',
err: err.code
});
}
else
{
res.send({
result: 'success',
err: '',
fields: fields,
json: rows,
length: rows.length
});
}
});
connection.release();
}
});
// connection.destroy();
});
app.post('/login/', function(req,res)
{
//debug for routes to make sure everything is working properly
console.log('I am in the login post route');
//connect to SQL pool
connectionPool.getConnection(function(err, connection)
{
if(err)
{
console.log('connection error: \n\n\n');
console.log(err);
res.statusCode = 503;
res.send({
result: 'error, having issue connecting to MYSQL DB instance',
err: err.code
});
}
else
{
var user = req.body.email;
user = user.toUpperCase();
var password = req.body.password;
console.log('user: ' + user);
console.log('password: ' + password);
var query = 'select COUNT(*) AS recordCount, isStaff from userTable where email = \''+user+'\' AND password = \''+password+'\'';
console.log(query);
connection.query(query, req.params.id, function(err, rows, fields)
{
if(err)
{
//another connection issue
console.log('in 500 error box')
console.log(err);
res.statusCode = 500;
res.send({
result: 'error',
err: err.code
});
}
else
{
//if the query was successful, we check to see if their exists a record of this query
//debug print count of records that match parameters
// console.log(rows[0].recordCount)
//if the return query has a user that has admin privileges, redirect them to the admin page
console.log(rows[0].isStaff);
if(rows[0].recordCount >=1 && rows[0].isStaff == 1)
{
console.log('at least one staff record')
res.sendFile(__dirname + '/views/admin.html')
// next();
}
else if(rows[0].recordCount >=1 && rows[0].isStaff == 0)
{
console.log('at least one nonstaff record')
res.sendFile(__dirname + '/views/customer.html')
// next();
}
else
{
console.log('invalid login')
console.log('in 503 error box, invalid user')
res.statusCode = 503;
res.send({
statuscode: '503',
result: 'E-mail or Password is incorrect',
});
}
}
});
connection.release();
}
});
});
Near the bottom of the code we specifically would like to handle the case when we have a login error. Right now it just sends back {{ statuscode: 503, result: 'E-mail or Password is incorrect'}} on a blank page.
On the front end a modal is displayed requesting sign in information. On success it redirects to a different page. On failure we would like to tell the front end to leave the modal on the page open and post an alert message in the body of the modal.
Please help.
Edit: The purpose of the project is working with the database. The project requires a web based app as the interface and since our next course requires using the MEAN stack we decided to go ahead and start learning a bit on our own.
In your $http call on the frontend, simply pass in a second argument as you error handling callback. The $http service will run that function any time the server sends back an error status.
https://docs.angularjs.org/api/ng/service/$http
$http.get('api/route')
.then(function successCallback(response) {
loginSuccessRedirect();
}, function errorCallback(response) {
handleLoginError();
});
I'm having difficulty setting up put and delete roots in a project the get and post methods at the same url are working fine. Currently, I'm getting an error 500 and a degree of debugging makes me think that the error is in the $http call. For the put route
factory.update(question) is called:
o.update = function(question) {
console.log('update was called')
return $http.put('/questions', question).success(function(data){
o.questions.push(data);
});
};
Which should call the put route in the express routes file:
/*UPDATE question*/
router.put('/questions', function(req, res, next){
console.log('made it to index.js');
var question = req.body;
question.title = req.body.title;
question.type = req.body.type;
question.key = req.body.key;
question.options = req.body.options;
question.save(function(err){
if (err) {
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
res.json(article);
}
});
});
For the delete route factory.remove(question) is called:
o.remove = function(question) {
return $http.delete('/questions', question).success(function(data){
o.questions.push(data);
});
}
Which should in turn call the delete route in the express routes file:
/*DELETE question*/
router.delete('/questions', function(req, res, next){
var question = req.body;
article.remove(function(err) {
if (err) {
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
res.json(article);
}
});
});
It seems that in both cases the methods in the express routes file are never called which is strange because nearly identical $http requests work elsewhere in the code just fine. How do access my api?
I'm trying to write an app that find a city in a MongoDB collection and uses the latitude and longitude it returns to find all zip codes within a certain distance. It seems to work, but the problem is that I'm getting an error that I can't set headers after they've already been sent. However, I've separated the to routes into different requests I don't understand why I'm still getting this error. What is the best way to make multiple calls to the API?
Here is my router in Node/Express:
// route to get city
app.get('/cities/:zip', function(req, res) {
// use mongoose to get the city in the database
console.log(req.params.zip);
var query = City.find({"zip" : req.params.zip});
query.exec(function(err, city) {
if (err)
res.send(err);
res.json(city);
});
});
// route to find cities within 50 miles
app.get('/matches/:latMin/:latMax/:lonMin/:lonMax', function(req, res) {
console.log(req.params.latMin + req.params.latMax + req.params.lonMin + req.params.lonMax);
var matches = City.find({latitude: {$gt: req.param.latMin, $lt:req.params.latMax }, longitude : {$gt :req.param.lonMin, $lt : req.param.lonMax}});
matches.exec(function(err, match){
if(err)
res.send(err);
console.log(match);
res.json(match);
});
});
app.get('*', function(req, res) {
res.sendfile('./public/views/index.html'); // load our public/index.html file
});
Here is my Angular Controller
$scope.update = function (zip) {
City.get({zip : zip}).success(function(response){
$scope.weather = response
}).then(function(response){
$scope.weather = response.data;
})
if(zip.length = 5){
$http.jsonp('http://api.openweathermap.org/data/2.5/weather?zip='+ zip +',us&callback=JSON_CALLBACK&units=imperial').success(function(data){
$scope.data=data;
});
var box = getBoundingBox([$scope.weather[0].latitude, $scope.weather[0].longitude], 50);
City.matches(box[1], box[3], box[0], box[2]).success(function(response){
$scope.matches = response
}).then(function(response){
$scope.matches = response.data;
console.log($scope.matches);
})
}
res.send does not return; the call continues to res.json. And please use braces. Please. Maybe they don't look cool or whatever. Just use them.
if (err) { handleError(res, err); return; }
res.status(200).json(city);
Further down, keeping things DRY:
function handleError(res, err) {
res.status(500).json(err);
}
Something seems to be wrong, either my server response or my save function.
In neither case success nor error function of model.save() gets invoked.
My server response:
exports.updateBusinessBannerAd = function(req, res) {
// update banner ad
db.insert(req.body, req.body._id, function(err, data) {
if (err) {
console.log('Update business banner ad: ' + err.message + ' ' + err['status-code']);
res.send(err['status-code'], { header: 'Update banner ad', message: err.message, type: 'error'});
}
else {
res.send(200);
}
});
};
And here my client code:
saveBannerAd: function(self) {
self.model.id = self.model.attributes._id;
self.model.save({
success: function(model, res) {
utils.growl( 'Banner Ad', 'has been updated!', 'info');
self.resetHandler();
},
error: function(model, res) {
console.log(res);
utils.growl(res.header, res.message, res.type);
return;
}
});
},
What am I missing?
Okay, adding { id: self.model.get('id') } as first argument to model.save() fixed my issue.
In this thread
someone suggested setting 'null' as first argument serves as placeholder, but my experience is that this will invoke the error function.