Mock Lambda.invoke wrapped but not being called - aws-lambda

I'm having an issue while trying to mock lambda.invoke which I'm calling from within another lambda function.
The function is wrapped (I can't use sinon after, it will tell me it's already wrapped).
The test keeps calling the lambda function on AWS instead of calling the mock.
It does the same if I use sinon instead of aws-sdk-mock.
test.js
const { handler1 } = require('../handler');
const sinon = require('sinon');
const AWSMock = require('aws-sdk-mock');
describe('invoke', () => {
beforeEach(() => {
invokeMock = jest.fn();
AWSMock.mock('Lambda', 'invoke', invokeMock);
// const mLambda = { invoke: sinon.stub().returnsThis(), promise: sinon.stub() };
// sinon.stub(AWS, 'Lambda').callsFake(() => mLambda);
});
afterEach(() => {
AWSMock.restore();
sinon.restore();
});
test('test1', async () => {
const event = { test: 'ok'};
const handler = await handler1(event);
expect(handler.statusCode).toBe(204);
});
});
and my lambda function is:
handler.js
const AWS = require('aws-sdk');
module.exports.handler1 = (event) => {
// The initialisation bellow has to be in the handler not outside.
const lambda = new AWS.Lambda({
region: 'us-west-2' //change to your region
});
let params = {
InvocationType: 'Event',
LogType: 'Tail',
FunctionName: 'handler2', // the lambda function we are going to invoke
Payload: JSON.stringify(event)
};
return new Promise((resolve, reject) => {
lambda.invoke(params, function(error, data) {
if(error) return reject(error);
const payload = JSON.parse(data.Payload);
if(!payload.success){
return resolve({ statusCode: 400});
}
return resolve({ statusCode: 204});
});
});
};
EDIT: So the issue I had was because I had my lambda initialisation (const lambda = new AWS.Lambda({})) outside the handler instead on inside. Thanks to stijndepestel's answer.

It is not entirely clear from the code you have shared, but presumably, you have a reference to lambda in your handler.js before you have wrapped the function in your test. Could you add the const lambda = new AWS.Lamda({}) line inside your handler function instead of outside of it?

Related

NextJS API Route Returns Before Data Received?

I'm not sure what's going on here. I have set up an API route in NextJS that returns before the data has been loaded. Can anyone point out any error here please?
I have this function that calls the data from makeRequest():
export async function getVendors() {
const vendors = await makeRequest(`Vendor.json`);
console.log({ vendors });
return vendors;
}
Then the route: /api/vendors.js
export default async (req, res) => {
const response = await getVendors();
return res.json(response);
};
And this is the makeRequest function:
const makeRequest = async (url) => {
// Get Auth Header
const axiosConfig = await getHeader();
// Intercept Rate Limited API Errors & Retry
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
await new Promise(function (res) {
setTimeout(function () {
res();
}, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
token[n] = null;
originalRequest._retry = true;
const refreshedHeader = await getHeader();
api.defaults.headers = refreshedHeader;
originalRequest.headers = refreshedHeader;
return Promise.resolve(api(originalRequest));
}
return Promise.reject(error);
}
);
// Call paginated API and return number of requests needed.
const getQueryCount = await api.get(url, axiosConfig).catch((error) => {
throw error;
});
const totalItems = parseInt(getQueryCount.data['#attributes'].count);
const queriesNeeded = Math.ceil(totalItems / 100);
// Loop through paginated API and push data to dataToReturn
const dataToReturn = [];
for (let i = 0; i < queriesNeeded; i++) {
setTimeout(async () => {
try {
const res = await api.get(`${url}?offset=${i * 100}`, axiosConfig);
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const { data } = res;
const arrayName = Object.keys(data)[1];
const selectedData = await data[arrayName];
selectedData.map((item) => {
dataToReturn.push(item);
});
if (i + 1 === queriesNeeded) {
console.log(dataToReturn);
return dataToReturn;
}
} catch (error) {
console.error(error);
}
}, 3000 * i);
}
};
The issue that I'm having is that getVendors() is returned before makeRequest() has finished getting the data.
Looks like your issue stems from your use of setTimeout. You're trying to return the data from inside the setTimeout call, and this won't work for a few reasons. So in this answer, I'll go over why I think it's not working as well as a potential solution for you.
setTimeout and the event loop
Take a look at this code snippet, what do you think will happen?
console.log('start')
setTimeout(() => console.log('timeout'), 1000)
console.log('end')
When you use setTimeout, the inner code is pulled out of the current event loop to run later. That's why end is logged before the timeout.
So when you use setTimeout to return the data, the function has already ended before the code inside the timeout even starts.
If you're new to the event loop, here's a really great talk: https://youtu.be/cCOL7MC4Pl0
returning inside setTimeout
However, there's another fundamental problem here. And it's that data returned inside of the setTimeout is the return value of the setTimeout function, not your parent function. Try running this, what do you think will happen?
const foo = () => {
setTimeout(() => {
return 'foo timeout'
}, 1000)
}
const bar = () => {
setTimeout(() => {
return 'bar timeout'
}, 1000)
return 'bar'
}
console.log(foo())
console.log(bar())
This is a result of a) the event loop mentioned above, and b) inside of the setTimeout, you're creating a new function with a new scope.
The solution
If you really need the setTimeout at the end, use a Promise. With a Promise, you can use the resolve parameter to resolve the outer promise from within the setTimeout.
const foo = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 1000)
})
}
const wrapper = async () => {
const returnedValue = await foo()
console.log(returnedValue)
}
wrapper()
Quick note
Since you're calling the setTimeout inside of an async function, you will likely want to move the setTimeout into it's own function. Otherwise, you are returning a nested promise.
// don't do this
const foo = async () => {
return new Promise((resolve) => resolve(true))
}
// because then the result is a promise
const result = await foo()
const trueResult = await result()

How to get rid of 'return new Promise' and not to lose data?

In this case, there is nothing easier to wait until the data is received, and then resolve the Promise:
// Using Promise resolve/reject
module.exports = () => {
return new Promise(async (resolve, reject) => {
let doc = await Document();
doc.on('data', async (data) => {
resolve(data);
});
})
}
But what do I do in this case?
// Using async/await
module.exports = async () => {
let doc = await Document();
doc.on('data', (data) => {
// ???
});
}
You still need the new Promise, you just should use it as the operand of an await inside the async function, not using an async function as the executor:
module.exports = async () => {
const doc = await Document();
return new Promise(resolve, reject) => {
doc.on('data', resolve);
});
};
However, I would recommend to use once instead of on so that the event handler is removed after the first occurrence of the event - the promise can be resolve only once anyway. Also if you have node v11.13.0 or higher you can just use the events.once method so that you don't have to build the promise yourself - and it also handles error events correctly:
const { once } = require('events');
module.exports = async () => {
const doc = await Document();
return once(doc, 'data');
};

AWS Lambda async/await boilerplate

Is there an example how to completely use async/await in aws lambda functions? Most lambda example starts with:
module.exports.handler = (event, context, callback) => {
Now I tried to use:
module.exports.hello = async (event, context) => {
but now I have to call a lambda-function in this lambda function.
Can I just write:
'use strict';
const AWS = require('aws-sdk');
const lambda = new AWS.Lambda({
region: 'my-region'
});
let params = {
FunctionName: process.env.lambdafunc, /* required */
Payload: "",
InvocationType: "Event"
};
module.exports.hello = async (event, context) => {
/** HERE COMES SOME CODE AND BUSINESS LOGIC
* ...
* ...
*/
params.Payload = new Buffer(JSON.stringify(MYJSONDATA));
data = await lambda.invokeAsync(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
return {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
data: data
}),
};
};

Query additional API Endpoint with Axios & Vue after getting results from a different endpoint

I'm utilising the following API for a World Cup Laravel app - http://api.football-data.org/docs/v1/index.html#_fixture
This information brings me back today's fixture's as I'm using this code (config just holds my API key):
const todaysMatches = new Vue({
el: '#todaysMatches',
data: {
todaysMatches: [],
flags: []
},
methods: {
loadData: function () {
axios.get("http://api.football-data.org/v1/competitions/467/fixtures/?timeFrame=p1", config)
.then(response => {this.todaysMatches = response.data});
}
},
mounted: function () {
this.loadData();
}
});
This brings back the following data sctructure:
Inside each fixture you get an array of _links which you can see in the below screenshot:
Now, what I would like to do is query both the awayTeam api and the homeTeam api because they each have an endpoint of crestUrl which returns the country's flag.
You can see that inside my data I've set an array prop called flags so I was thinking of running additional calls inside my loadData method and populate that array for each fixture, but I don't think that's a clean way of doing it.
Can anyone suggest the best way to approach this?
I have used async/await pattern to achieve your requirement as below:
loadData: async function() {
const response = await axios.get(
"http://api.football-data.org/v1/competitions/467/fixtures/?timeFrame=p1",
config
);
this.todaysMatches = response.data;
let arr = this.todaysMatches.fixtures.map(fixture => {
const _links = fixture._links;
return [
axios.get(_links.awayTeam.href, config),
axios.get(_links.homeTeam.href, config)
];
});
arr.forEach(async item => {
const away = await item[0];
const home = await item[1];
this.flags.push({
awayFlag: away.data.crestUrl,
homeFlag: home.data.crestUrl
});
});
}
Explaination:
After fetching todaysMatches a new array arr is created which consists of promises returned by get request to the team's url [[getAwayTeamInfo, getHomeTeamInfo], [getAwayTeamInfo, getHomeTeamInfo], [getAwayTeamInfo, getHomeTeamInfo],...]
We loop through this and await on the promise to get the crestUrl
This crestUrl is pushed into flags array as an object
{
awayFlag: away.data.crestUrl,
homeFlag: home.data.crestUrl
}
Update
Adding the flag urls directly to the this.todaysMatches.fixtures array
loadData: async function() {
const response = await axios.get(
"http://api.football-data.org/v1/competitions/467/fixtures/?timeFrame=p1",
config
);
this.todaysMatches = response.data;
const fixtures = this.todaysMatches.fixtures;
let arr = fixtures.map(fixture => {
const _links = fixture._links;
return [
axios.get(_links.awayTeam.href, config),
axios.get(_links.homeTeam.href, config)
];
});
arr.forEach(async (item, index) => {
const away = await item[0];
const home = await item[1];
this.$set(fixtures, index, {
...fixtures[index],
awayFlag: away.data.crestUrl,
homeFlag: home.data.crestUrl
});
});
}

useFakeTimers mocha chai sinon - not the right result on a test

I am trying to run a test where I want to verify that my helper file is running correctly, and if I have an expired token, I get an error kickback and cannot proceed.
I have a feeling that I can only fake the time directly in the test, and not outside of it. Thing is, I don't want to copy the jwt.verify function in my test because that defeats the purpose if I change the code in the actual helper file. Any help on this one to make this work?
I am faking the time with sinon. If I test to see what time I get now and after the clock tick, I do get the right results. But for some reason this is not applying to the function in another file.
my local.js file
const moment = require('moment');
const jwt = require('jsonwebtoken');
const secret = process.env.TOKEN_SECRET;
function encodeToken(user) {
const playload = {
exp: moment().add(1, 'hours').unix(), // expires the token in an hour
iat: moment().unix(),
sub: user.id
};
return jwt.sign(playload, secret);
}
function decodeToken(token, callback) {
const payload = jwt.verify(token, secret, function (err, decoded) {
const now = moment().unix();
console.log('tim: ' + decoded.exp); //just to see
console.log('now: ' + now); // just to see
if (now > decoded.exp) {
callback('Token has expired.');
}
callback(null, decoded);
});
}
module.exports = {
encodeToken,
decodeToken
};
and my test file:
process.env.NODE_ENV = 'test';
const chai = require('chai');
const should = chai.should();
const sinon = require('sinon');
const localAuth = require('../../src/server/auth/local');
describe('decodeToken()', function () {
var clock;
beforeEach(function () {
clock = sinon.useFakeTimers();
});
afterEach(function () {
clock.restore();
});
it('should return a decoded payload', function (done) {
const token = localAuth.encodeToken({
id: 1
});
should.exist(token);
token.should.be.a('string');
clock.tick(36001000000);
localAuth.decodeToken(token, (err, res) => {
should.exist(err);
res.should.eql('Token has expired.');
done();
});
});
});
JWT checks the expiry and throws error by itself. So we just have to assert from the error message. I have made some changes to the code and made it working.
I tested this as below, (code snippets)
const moment = require('moment');
const jwt = require('jsonwebtoken');
const secret = 'abczzxczxczxc';
function encodeToken(user) {
const payload = {
exp: moment().add(1, 'hours').unix(), // expires the token in an hour
iat: moment().unix(),
sub: user.id
};
const token = jwt.sign(payload, secret);
return token;
}
function decodeToken(token, callback) {
jwt.verify(token, secret, function(err, decoded) {
callback(err, decoded);
});
}
module.exports = {
encodeToken,
decodeToken
};
Tested as below,
process.env.NODE_ENV = 'test';
const chai = require('chai');
const should = chai.should();
const sinon = require('sinon');
const localAuth = require('./');
describe('decodeToken()', function () {
var clock;
beforeEach(function () {
clock = sinon.useFakeTimers();
});
afterEach(function () {
clock.restore();
});
it('should return a decoded payload', function (done) {
const token = localAuth.encodeToken({
id: 1
});
token.should.exist;
token.should.be.a('string');
clock.tick(36001000000);
localAuth.decodeToken(token, (err, res) => {
should.exist(err);
err.message.should.eql('jwt expired');
done();
});
});
});
Output
➜ faketimer ./node_modules/mocha/bin/mocha index_test.js
decodeToken()
✓ should return a decoded payload
1 passing (17ms)

Resources