Stubbing request across entire project - javascript

I'm writing tests for a small REST library that implements OAuth's refresh grant on top of the request library. As part of it's functionality, it's providing a retry function that has something like this in rest.js:
const auth = require('./auth');
const request = require('request');
function retry(headers, responseHandler) {
headers.auth.bearer = auth.getToken();
request(headers, function(err, resp) {
if (resp.error && resp.error == 'invalid_token') {
return auth.renew(function(e, r) { retry(headers, responseHandler); });
}
// handle happy case
});
});
What auth.renew does is posting to the token provider with the information in headers and internally known refresh token. The important bit is that it does this by using request.post, passing the recursive event handler down to the request library.
Naturally, when I test this, I don't want outgoing HTTP calls. So I use sinon to stub them out:
const requestStub = sinon.stub(),
rest = proxyquire('./rest', { 'request': requestStub }),
authFail = { error: 'invalid_token' },
authSuccess = { token: '123' };
describe('#retry', () => {
it('calls retry twice on failed auth', () => {
requestStub.yields(null, authFail);
requestStub.post = sinon.stub().yields(null, authSuccess);
retry({}, () => {});
sinon.assert.calledTwice(requestStub);
});
});
The problem is that auth.renew happily goes on to require('request') on it's own and thus never seeing my stub. So I guess my question is:
How do I make auth use my stubbed request instead of its own?
I know sinon can stub XHR, but that seems like a lot of low-level effort for what I want.

So the solution I came up with was to replace the entire auth.renew method, so the thing looks like this:
const requestStub = sinon.stub(),
rest = proxyquire('./rest', { 'request': requestStub,
'./auth': {
renew: function(callback) {
requestStub.yields(null, authSuccess);
callback();
}
}),
authFail = { error: 'invalid_token' },
authSuccess = { token: '123' };
describe('#retry', () => {
it('calls retry twice on failed auth', () => {
requestStub.yields(null, authFail);
retry({}, () => {});
sinon.assert.calledTwice(requestStub);
});
});

Related

Unit test a custom authorizer lambda

I have a customized authorizer lambda which takes an event from a API Gateway and calls another internal api which validates if a user has access to a particular resource.
This API takes in a jwttoken and a id and returns back response.
I have been trying to write unit test for this lambda. I am lost as to how can I mock data - specifically the token and id.
Here is what I wrote (Jest)
describe('lambdaService', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
test('should return data', async () => {
const response = { statusCode: 200 };
const event = {
"headers" : {
'Authorization' : 'token'
},
"pathParameters": {
"proxy": "/my-path/{id}"
},
}
const retrieveData = jest.spyOn(authorizeResourceJWTUser, 'doGetRequest').mockResolvedValueOnce(response)
const actualValue = await handler(event);
expect(actualValue).toEqual(response);
});
const {validate} = require('./helper/validateResource')
exports.handler = async (event, context, callback) => {
try {
let id, token, HOST
id = extractId(path)//some function to return id from url
let response = await validate(id, token)
callback(null,generatePolicy(...)
} catch(error) {
callback("Unauthorized")
}
};
function generatePolicy {...};
But it fails giving 401 error since the token and the {id} is not valid.
There is no proper documentation on how can I mock this API call rather than calling the actual service

How do you receive Whatsapp messages from Twilio using Node.JS?

I am trying to build a Whatsapp chatbot using Node.JS and am running into a bit of trouble in receiving the Whatsapp message from Twilio. On checking the debugger, I get a Bad Gateway error, ie. Error 11200: HTTP Retrieval Failure. The message is getting sent, and ngrok shows the post request, however, dialogflow does not receive the request. On terminal, the error is showing UnhandledPromiseRejectionWarning: Error: 3 INVALID ARGUMENT: Input text not set. I'm not sure if it's because the message is not in JSON format. Please help!
This is the app.post function:
app.post('/api/whatsapp_query', async (req, res) =>{
message = req.body;
chatbot.textQuery(message.body, message.parameters).then(result => {
twilio.sendMessage(message.from, message.to, result.fulfillmentText).then(result => {
console.log(result);
}).catch(error => {
console.error("Error is: ", error);
});
return response.status(200).send("Success");
})
});
And this is the sendMessage function I've imported:
const config = require('./config/keys');
const twilioAccountID = config.twilioAccountID;
const twilioAuthToken = config.twilioAuthToken;
const myPhoneNumber = config.myPhoneNumber;
const client = require('twilio')(twilioAccountID,twilioAuthToken);
module.exports = {
sendMessage: async function(to, from, body) {
return new Promise((resolve, reject) => {
client.messages.create({
to,
from,
body
}).then(message => {
resolve(message.sid);
}).catch(error => {
reject(error);
});
});
}
}
And this is the textQuery function I've imported:
textQuery: async function(text, parameters = {}) {
let self = module.exports;
const request = {
session: sessionPath,
queryInput: {
text: {
text: text,
languageCode: config.dialogFlowSessionLanguageCode
},
},
queryParams: {
payload: {
date: parameters
}
}
};
let responses = await sessionClient.detectIntent(request);
responses = await self.handleAction(responses)
return responses[0].queryResult;
},
Twilio developer evangelist here.
The issue is that you are not passing the correct message body from the incoming WhatsApp message to your textQuery function.
First, you should make sure that you are treating the incoming webhook from Twilio as application/x-www-form-urlencoded. If you are using body-parser, ensure you have urlencoded parsing turned on.
app.use(bodyParser.urlencoded());
Secondly, the parameters that Twilio sends start with a capital letter. So your code currently gets message = req.body and then uses message.body. But it should be message.Body.
Those two points should sort you out.
One final thing though. The Twilio Node.js library will return a Promise if you do not pass a callback function. So you don't need to create a Promise here:
module.exports = {
sendMessage: async function(to, from, body) {
return new Promise((resolve, reject) => {
client.messages.create({
to,
from,
body
}).then(message => {
resolve(message.sid);
}).catch(error => {
reject(error);
});
});
}
}
You can just return the result of the call to client.messages.create
module.exports = {
sendMessage: async function(to, from, body) {
return client.messages.create({ to, from, body });
}
}
Hope this helps.

NodeJs facing the issue while using async waterfall

I am calling 2 API request 1 after another so I decided to use the waterfall model but I am facing the issue in it
I have tried so much but not able to solve the issue.
Below is my code:
var unirest = require("unirest");
var async = require("async")
exports.user = (req, res, next) => {
const qry = req.params.id
async.waterfall([
(nextCall) => {
var req = unirest("GET", API_URL1);
req.query({
// some query
});
req.headers({
// some headers
});
req.end(function(subCount) {
// if (resp.error) throw new Error(resp.error);
var channelSubCount = subCount.body
nextCall(null, data)
});
},
(data, nextCall => {
console.log(channelSubCount, 'data')
var reqs = unirest("GET", API_URL2);
reqs.query({
// some query
});
reqs.headers({
// some headers
});
reqs.end(function(res) {
// if (res.error) throw new Error(res.error);
console.log(res.body);
return nextCall(null, {
name: 'abc',
photo: 'src',
count: data
})
});
})
], function(finalData) {
// if (error) { alert('Something is wrong!'); }
console.log('final')
res.status(200).json(
finalData
);
});
};
ERROR:
Reference Error: data is not defined
I don't understand why this is happening.
Also some please show me the right way to implement the above things with optimizations.
Any help appreciated...
Looks like you forgot to close parentheses here in your second arrow function definition:
(data, nextCall => {
It's still a valid JavaScript, but the interpreter now treats data not as a function incoming parameter (as you need), but as a variable. But it's not defined anywhere, therefore you have that error.
Make it like this and it will work:
(data, nextCall) => {

FeathersJS: Injecting HTTP headers in Service Test

In a feathersJS service, I have a before hook being ran that expects a certain HTTP header to exist:
src/services/service_name/service_name.hooks.js
const validationHook = () => (context, next) => {
if (!context.params.headers.hasOwnProperty('header-wanted'))
throw new errors.BadRequest();
next(null, context);
};
module.exports = {
before: {
all: [cronValidationHook()],
...
..
.
When testing this service in a generated test file from feathers-cli, however, I haven't found a way to inject headers prior to the before hook being called. The test in question is:
test/services/service_name.test.js
describe('get', () => {
it('should run "id" endpoint', async () => {
const service = app.service('v1/cron');
const resp = await service.get('id', params);
// Assertions exist after this call
});
});
Is there a way to do this that does not require utilizing an HTTP call via node-fetch or requests?
params will be whatever you pass. Just set params.headers to what you would like to test, e.g.
const getParams = {
...params,
headers: { 'header-wanted': 'something' }
};
const resp = await service.get('id', getParams);

CycleJs - Unit Testing a Component with Mocha

I'm learning in cyclejs and rxjs and I'm struggling to write a unit test for one of the components I'm working on. I'm sorry for the long question. I have tried to simplify the question and this is the best I can do.
Component does the following things:
1) When it's loaded, it sends a request to an end point to retrieve user's tasks. From these tasks, it finds the task user currently working on and creates an url stream which contains the endpoint which will be called when user clicks complete task button.
2) When a new value come to test stream, it creates a new stream from tests defined so far.
3) When user clicks complete task, component first sends a request to an endpoint to add tests user defined to the task. Then, if the call is successful, component sends another request to the endpoint which is retrieved at step 1 to complete task.
4) After complete task request is completed, component shows a notification to the user.
Here is the component:
import Rx from 'rx';
import {Observable} from 'rx';
const Model = ({test$, completeTask$, sources}) => {
const processId = Number(sources.routeParams.processId);
const SET_TEST_RESULTS = `http://dummyhost/tosettests/${processId}`;
const GET_USER_TASKS = 'http://dummyhost/togetusertasks';
const taskRequest$ = Observable.just({
url: GET_USER_TASKS,
method: 'GET'
});
const url$ = sources.HTTP
.filter(response => response.request.url === GET_USER_TASKS)
.flatMap(response => response.body)
.filter(task => task.processId === processId)
.take(1)
.map(task=> {
const activitiId = task.processInstanceId;
return {
completeTask: `http://dummyhost/tocompletetask/${activitiId}`
};
});
const definedTest$ = test$
.scan((acc, curr) => {
acc.push(curr);
return acc;
}, [])
.startWith([])
.share();
const setTestResultsRequest$ = completeTask$
.withLatestFrom(definedTest$, (event, tests) => {
return {
url: SET_TEST_RESULTS,
method: 'POST',
send: {
tests
}
};
});
const setTestResultsResponse$ = sources.HTTP
.filter(response => response.request.url === SET_TEST_RESULTS);
const completeTaskRequest$ = setTestResultsResponse$
.withLatestFrom(url$, (response, url) => {
return {
url: url.completeTask,
method: 'POST'
};
});
const completeTaskResponse$ = sources.HTTP
.withLatestFrom(url$, (response, url) => {
return {
response,
url
};
})
.filter(data => data.response.request.url === data.url.completeTask)
.map((data)=> data.response);
const successNotification$ = completeTaskResponse$
.map(() => {
return {
action: 'success',
message: 'Tests have been defined!'
};
});
const request$ = Observable.merge(taskRequest$, setTestResultsRequest$, completeTaskRequest$);
return {
request$: request$,
notification$: successNotification$,
definedTest$: definedTest$
};
};
What I'm trying to unit test are the followings:
1) Component must send a request to define tests to the task
2) Component must send a request to complete task
3) Component must show a notification
Here's the unit test I wrote so far with Mocha:
describe('When complete task is clicked', ()=> {
const tests = [
{
name: 'test 1 name'
},
{
name: 'test 2 name'
},
{
name: 'test 3 name'
}
];
const test$ = Observable.fromArray(tests);
const completeTask$ = Observable.just({}).delay(300);
const processId = 3;
const activitiId = 3;
var actions;
before(done => {
const HTTPSource = new Rx.ReplaySubject();
const sources = {
DOM: mockDOMSource(),
routeParams: {processId},
HTTP: HTTPSource
};
actions = Model({test$, completeTask$, sources});
const request$ = actions.request$.share();
request$
.filter(request=> request.url === 'http://dummyhost/togetusertasks')
.subscribe((request) => {
const response = {
request,
body: [{
processId,
processInstanceId: activitiId
}]
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
done();
});
});
it('should send a request to add tests', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe(() => {
done();
});
});
it('should send a request to complete task', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe(() => {
done();
});
});
it('should show a notification to inform user that tests have been defined', (done) => {
actions.notification$.take(1)
.subscribe(notification => {
expect(notification.action).to.equal('success');
done();
});
});
});
In the test, I have tried to mock HTTP source. By subscribing request stream sink returning from component, I'm able to create mock responses for each request. But I have to use share operator to run mocha's before function successfully. Otherwise, I'm only able to mock first request which is retrieving user's tasks. So, my first question is:
Why do I have to use share operator to run mocha's before function successfully?
Currently, tests that verify complete task request is sent and notification is shown passes. But, test that verifies define test to the task request is failing. This is strange because to verify complete task request is sent, component must send define tests to the task request first. It seems define tests to the task request somehow lost in the request stream returned from component's sink. So my second question is:
Why second and third tests are passing but first test is failing?
Here's a gist that contains all code in one file:
gist link
As I said earlier, I'm new to cyclejs and rxjs. I think I'm not fully understanding what I'm testing. So any comments or answers about cyclejs and rxjs that directs me the right path are welcomed.
If you think question is not clear enough, please let me know. I'll do my best improve the question.

Categories