Passing res wrapped in object into callback fails - javascript

I have a temporary server that looks like this:
import http from 'http'
export default function tempServer(
port: number
): Promise<{ req: http.IncomingMessage; res: http.ServerResponse }> {
return new Promise((resolve) => {
const server = http
.createServer(function (req, res) {
resolve({ req, res })
// This works
// res.write('Hey!')
// res.end()
server.close()
})
.listen(port)
})
}
And I am trying to manipulate res by calling res.write in another function:
function () {
const server = tempServer(parseInt(process.env.AUTH_PORT) || 5671)
return server.then(({req, res}) => {
const data = url.parse(req.url, true).query
...
res.write('Error: ' + data.error)
res.end()
...
})
}
The result is that res.write and res.write have no effect. I am sure the issue has something to do with contexts and bindings but I am having trouble going about it. Anyone willing to indulge me?

Related

Await function returning undefined response object

I am not much experienced with async/await, but I have incorporated the keywords in a function which basically fetch or POST data to MongoDB database. It seems like await does not wait for the promise to be fulfilled and returns an undefined response object.
I am getting the following error:
res.error(404).json({message: e.message})
^
TypeError: Cannot read properties of undefined (reading 'error')
at Object.getAllItems (/Users/myName/Desktop/TODOList/backend/Controllers/ItemsConroller.js:12:13)
Here is my code:
ItemsController.js:
const getAllItems = async (req, res) => {
try{
const items = await Item.find({})
res.status(200).json(items)
}
catch(e){
res.error(404).json({message: e.message})
}
}
Server.js file:
app.get('/todos', (req, res) => {
dbItem.getAllItems().then(data => {
console.log("entered")
res.send(data);
})
})
Same problem with other functions in the controller file which incorporates await/async keywords.
Thanks.
You are not passing your req and res object to your /todos endpoint:
To make you app more robust try this:
You can use getAllItems as your middleware like this
app.get('/todos', dbItem.getAllItems, (req, res) => {
if(!req.error){
res.send(req.items)
}else{
res.send(req.error)
}
})
And attach your variables to the req object and pass them to /todos
const getAllItems = async (req, res, next) => {
try{
const items = await Item.find({})
req.items = items
next()
}
catch(e){
req.error = e.message
next()
}
}

Do we need to wrap app.get in async function

I've come across this code
(async () => {
app.get('/health', (req: Request, res: Response) => {
res.send();
});
more endpoints here
....
})();
I don't get why we need to wrap app.get in async here, is that necessary?
Probably not, but we'd need more context to be sure. As shown, no, there's no point.
It may be that they were relying on information they only got asynchronously for setting up the routes and wanted to use await rather than .then/.catch when using that information, e.g.:
(async () => {
try {
app.get('/health', (req: Request, res: Response) => {
res.send();
});
const moreRoutes = await getMoreRoutes();
// −−−−−−−−−−−−−−−−^^^^^
for (const route of moreRoutes) {
// ...set up more routes
}
} catch (e) {
// Handle/report error
}
})();
If so, hopefully they have a try/catch around the entire body (as shown above) or a .catch at the end like this:
(async () => {
app.get('/health', (req: Request, res: Response) => {
res.send();
});
const moreRoutes = await getMoreRoutes();
// −−−−−−−−−−−−−−−−^^^^^
for (const route of moreRoutes) {
// ...set up more routes
}
})().catch(e => {
// Handle/report error
});
async/await can make it much easier (IMHO) to read code. But the above can be done with .then/.catch as well:
app.get('/health', (req: Request, res: Response) => {
res.send();
});
getMoreRoutes()
.then(moreRoutes => {
// ...set up more routes
})
.catch(e => {
// Handle/report error
});

How to define a function which returns Promise to a Express Route function?

I have a business leve database module called "db_location" which uses the node-fetch module to get some data from a remote server via REST API.
**db_location.js** DB LOGIC
const p_conf = require('../parse_config');
const db_location = {
getLocations: function() {
fetch(`${p_conf.SERVER_URL}/parse` + '/classes/GCUR_LOCATION', { method: 'GET', headers: {
'X-Parse-Application-Id': 'APPLICATION_ID',
'X-Parse-REST-API-Key': 'restAPIKey'
}})
.then( res1 => {
//console.log("res1.json(): " + res1.json());
return res1;
})
.catch((error) => {
console.log(error);
return Promise.reject(new Error(error));
})
}
};
module.exports = db_location
I would need to call this function within a Route function so as to separate database processing from controller.
**locations.js** ROUTE
var path = require('path');
var express = require('express');
var fetch = require('node-fetch');
var router = express.Router();
const db_location = require('../db/db_location');
/* GET route root page. */
router.get('/', function(req, res, next) {
db_location.getLocations()
.then(res1 => res1.json())
.then(json => res.send(json["results"]))
.catch((err) => {
console.log(err);
return next(err);
})
});
When I ran http://localhost:3000/locations, I received the following error.
Cannot read property 'then' of undefined
TypeError: Cannot read property 'then' of undefined
It seems the Promise was empty or something wrong down the Promise chain going from one response object to another? What is a best practise for solving this kind of scenario?
EDIT 1
If I changed the getLocations to return res1.json() (which I think is a non-empty Promise according to the node-fetch documentation):
fetch(`${p_conf.SERVER_URL}/parse` + '/classes/GCUR_LOCATION', { method: 'GET', headers: {
'X-Parse-Application-Id': 'APPLICATION_ID',
'X-Parse-REST-API-Key': 'restAPIKey'
}})
.then( res1 => {
return res1.json(); // Not empty as it can be logged to `Promise Object`
})
.catch((error) => {
console.log(error);
return Promise.reject(new Error(error));
})
And the route code was changed to :
db_location.getLocations()
.then(json => res.send(json["results"]))
.catch((err) => {
console.log(err);
return next(err);
})
The exactly same error was thrown.
You need getLocations to return a Promise. At the moment, it's running a fetch, but that fetch isn't connected to anything else, and getLocations is returning undefined (and of course you can't call .then on uundefined)
Instead, change to:
const db_location = {
getLocations: function() {
return fetch( ...
Also, since you're not doing anything special in the getLocations catch block, you might consider omitting it entirely and let the caller handle it.
Your function doesn't return anything.
If you want to use a promise, you need return it.

jest everything after expect isn't called

my jest is not working as I expect it. See:
const res = {
send: (content) => {
expect(content).toEqual({
app_status: 501,
errors: {
jwt: {
location: 'body',
param: 'jwt',
value: undefined,
msg: 'The jwt is required'
}
}
});
console.log("after expect");
done();
},
};
Basically EVERYTHING after the expect(content).toEqual ... in res.send is not called. I find that very confusing. I am getting no error except for that my test's are taking too long (because done) is not called and the test is not "closed". So my question is, am I missing something obviously?
The following should work fine. I added asynchronous since send may be called asynchronously:
const createResponse = () => {
var resolve;
const p = new Promise(
(r,reject)=>resolve=r
);
return [
{
send: (value) => {
resolve(value)
}
},
p
];
};
test('(async) fail', done => {
//Router
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
setTimeout(x=>res.send('v1'),10)
});
const request = {
method: 'GET'
};
let [response,p] = createResponse()
router.stack[0].handle(request, response, () => {});
p.then(
x=>expect(x).toBe('v2'),
reject=>expect("should not reject").toBe(reject)
).then(
x=>done()
//,x=>done() //this will cause all tests to pass
);
});
To answer your question in the comment; consider the following code:
const express = require('express');
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
res.send("Hello World");
});
const request = {
method: 'GET'
};
const response = {
send: (value) => {
debugger;//pause here and see the stack
throw new Error("Hello Error.");
}
};
router.stack[0].handle(
request,
response,
//this is the function done in route.js:
// https://github.com/expressjs/express/blob/master/lib/router/route.js#L127
err => {
console.error(err.message);//console errors Hello Error.
}
);
Send throws an error but send is called by your mock which is called by express here. So express catches the exception and then ends up here (done is not from just but your callback).
So it'll call your callback with an error, skipping done from jist and not throwing anything (maybe showing something in log). Since your callback doesn't do anything it times out.
You could try to call done from jist in the callback (at console.error(err.message);).
[UPDATE]
Careful trying to catch the error thrown by expect the following will tell me 1 test passed:
test('(async) fail', done => {
try{
expect(true).toBe(false);
}catch(e){
}
done();
});

Jest testing mongoose model instantiation

I'm trying to test a REST API built with express and mongoose, I'm using jest and supertest for the http calls; also I'm relatively new to testing with javascript.
When testing a creation url I wan't to make sure the instantiation is called using just the req.body object but I'm not sure how to do it, after reading a lot about differences between mock objects and stubs and some of the Jest documentation my last try looks like this:
test('Should instantiate the model using req.body', done => {
const postMock = jest.fn();
const testPost = {
name: 'Test post',
content: 'Hello'
};
postMock.bind(Post); // <- Post is my model
// I mock the save function so it doesn't use the db at all
Post.prototype.save = jest.fn(cb => cb(null, testPost));
// Supertest call
request(app).post('/posts/')
.send(testPost)
.then(() => {
expect(postMock.mock.calls[0][0]).toEqual(testPost);
done();
})
.catch(err => {throw err});
});
Also I would like to know how to manually fail the test on the promise rejection, so it doesn't throws the Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
As it stands, you're performing more of a integration test rather than isolating the route handler function itself and testing just that.
First I would break away the handler for /posts/ to its own file (assuming you haven't done this already):
controllers/post-controller.js
const Post = require('./path/to/models/post')
exports.store = async (req, res) => {
const post = await new Post(req.body).save()
res.json({ data: post }
}
Next simply use the handler wherever you defined your routes:
const express = require('express')
const app = express()
const postController = require('./path/to/controllers/post-controller')
app.post('/posts', postController.store)
With this abstraction we can now isolate our postController.store and test that it works with req.body. Now since we need to mock mongoose to avoid hitting an actual database, you can create a mocked Post like so (using the code you already have):
path/to/models/__mocks__/post.js
const post = require('../post')
const mockedPost = jest.fn()
mockedPost.bind(Post)
const testPost = {
name: 'Test post',
content: 'Hello'
}
Post.prototype.save = jest.fn(cb => {
if (typeof cb === 'function') {
if (process.env.FORCE_FAIL === 'true') {
process.nextTick(cb(new Error(), null))
} else {
process.nextTick(cb(null, testPost))
}
} else {
return new Promise((resolve, reject) => {
if (process.env.FORCE_FAIL === 'true') {
reject(new Error())
} else {
resolve(testPost)
}
})
}
})
module.exports = mockedPost
Notice the check for process.env.FORCE_FAIL if for whatever reason you wanted to fail it.
Now we're ready to test that using the req.body works:
post-controller.test.js
// Loads anything contained in `models/__mocks__` folder
jest.mock('../location/to/models')
const postController = require('../location/to/controllers/post-controller')
describe('controllers.Post', () => {
/**
* Mocked Express Request object.
*/
let req
/**
* Mocked Express Response object.
*/
let res
beforeEach(() => {
req = {
body: {}
}
res = {
data: null,
json(payload) {
this.data = JSON.stringify(payload)
}
}
})
describe('.store()', () => {
test('should create a new post', async () => {
req.body = { ... }
await postController(req, res)
expect(res.data).toBeDefined()
...
})
test('fails creating a post', () => {
process.env.FORCE_FAIL = true
req.body = { ... }
try {
await postController.store(req, res)
} catch (error) {
expect(res.data).not.toBeDefined()
...
}
})
})
})
This code is untested, but I hope it helps in your testing.

Categories

Resources