How can I use node-mocks-http when mocking the AWS SDK? - node.js

I've got an existing working test that is testing a route in an Express app (trimmed code):
const AWS = require('aws-sdk-mock');
const AWS_SDK = require('aws-sdk');
AWS.setSDKInstance(AWS_SDK);
...
before(() => {
sendEmailMock = sinon.stub().callsArgWith(1, null, 'All is well');
AWS.mock('SES', 'sendEmail', sendEmailMock);
server = rewire('../../../..');
...
describe('POST:/feedback', () => {
it('Returns 200 with a fully formed request', (done) => {
request(app)
.post('/gethelp/api/v1/feedback')
.send({
thumbsUp: 'true',
title: 'Abcdef ghi',
url: 'http://google.com',
comments: 'lkajsdj lkajsdkjf aslkjdfa asjdflasjd lkfj',
})
.expect(200, () => {
const args = sendEmailMock.args[0][0];
... etc
This is a working test. But I need to refactor it to not use the full server (because it's doing some integration stuff on startup). So I'm bringing in node-mocks-http:
const httpMocks = require('node-mocks-http');
const feedbackRouteHandler = require('./feedback');
...
before(() => {
sendEmailMock = sinon.stub().callsArgWith(1, null, 'All is well');
AWS.mock('SES', 'sendEmail', sendEmailMock);
});
...
const mockRequest = httpMocks.createRequest({
method: 'POST',
url: '/gethelp/api/v1/feedback',
body: {
thumbsUp: 'true',
title: 'Abcdef ghi',
url: 'http://google.com',
comments: 'lkajsdj lkajsdkjf aslkjdfa asjdflasjd lkfj',
},
});
const mockResponse = httpMocks.createResponse();
feedbackRouteHandler(mockRequest, mockResponse);
expect(mockResponse.statusCode).to.equal(200);
expect(sendEmailMock.args).to.exist;
The problem is that adding in node-mocks-http appears to have broken the mocking of the AWS SDK. When sendEmail is hit it's hitting the actual AWS SDK, not the mocked version. It was hitting the mocked version in the previous version of the test.
How can I use node-mocks-http with aws-sdk-mock?

Related

Call cloud function through another in the same project without allUsers permission

I have 2 functions in the same google cloud functions project (myfunction1 and myfunction2.
exports.myfunction1 = async (req, res) => {
await axios({
method: 'post',
url: 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2',
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
data: myjson
}).then(response => {
console.log(JSON.stringify(response.data));
}).catch(err => {
console.error("catch error");
console.error(err);
})
}
It is works fine, but only if I configure invokers permission for allUsers. If I remove this permission, e receive 403 code error. Not sounds good keep this permisson activate, because the function is exposed. I tried solve with this link and this link, but, no sucess.
Edit1:
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const targetAudience = 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2'
const url = '??????????';
async function request() {
console.info('request ${url} with target audience ${targetAudience}');
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}
I'm trying using this code, but, who is const url?
You must perform service to service authentication. You can find a great tutorial in the Cloud Run page (ok you use Cloud Functions but the underlying infrastructure is the same and the doc is better).
You also have to be aware about the Functions identity and how to change them (or to grant the current service account the correct permission)
let audience = 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2';
let token_request_url = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=' + audience;
var token_response = await axios.get(token_request_url, { headers: {'Metadata-Flavor': 'Google'} });
let token_auth = token_response.data;
axios({
method: 'post',
url: audience,
timeout: 15000,
headers: {
'Authorization': "Bearer " + token_auth
},
data: myJSON
}).catch(err => {
console.error(err);
});

How to set zapier cli authentication?

I am trying to build an app using zapier cli.
I am having a problem with authorizing the request .
I am using an api key and a client id.
When I try to use the same credentials in the UI
Its working perfect , however in the cli it gives an error code 403.
I have listed the code below . What could be the issue ?
//test
"use strict";
const should = require("should");
const zapier = require("zapier-platform-core");
const App = require("../index");
const appTester = zapier.createAppTester(App);
describe("custom authentication authentication", () => {
// Put your test TEST_USERNAME and TEST_PASSWORD in a .env file.
// The inject method will load them and make them available to use in your
// tests.
zapier.tools.env.inject();
it("should authenticate", (done) => {
const bundle = {
authData: {
api_key: process.env.API_KEY,
client_id: process.env.CLIENT_ID,
},
};
appTester(App.authentication, bundle)
.then((response) => {
should.exist(response);
done();
})
.catch(done);
});
});
//authentication.js
"use strict";
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1;
const day = currentDate.getDate();
const authentication = (z, bundle) => {
const options = {
url: "url",
method: "GET",
headers: {
ContentType: "application/json",
Accept: "application/json",
"x-api-key": bundle.authData["api_key"],
client_id: bundle.authData["client_id"],
},
params: {
year: year,
month: month,
day: day,
page_size: "1000",
},
};
return z.request(options).then((response) => {
response.throwForStatus();
});
};
module.exports = authentication;
Do you have values in a .env file at the application root? You can also double check that console.log(process.env.API_KEY) prints a value when the test is running.
As an aside, you can use the zapier convert CLI command to copy your integration from the UI to the CLI without having to re-write it. If it works in the UI, it'll work in the CLI too.

Nock does not intercept requests - Error: getaddrinfo ENOTFOUND

Let me first explain how everything is set up.
The tests are run using Jest.
I have a jest.config.json with the following line:
"globalSetup": "<rootDir>/__tests__/setup.js",
In setup.js I have the following content:
As you can see, I put the fake URL in the environment variable AMA_API.
After that, I require ./nock.ts and log the line Nock init done. The rest of the content of this file seems irrelevant to me.
module.exports = async function() {
console.log('[JEST SETUP] setting env var')
process.env.AMA_API = 'http://fake.local'
require('tsconfig-paths').register()
require('ts-node').register({
lazy: true,
fast: true,
project: 'tsconfig.json'
})
require('./nock.ts')
console.log('Nock init done.')
const connections = await require('src/db/connection.ts').initConnection()
await require('./clearDb.ts').clearDB()
await require('./initDb.ts').initializeDB()
await Promise.all(connections.map(con => con.close()))
console.log(`Connection setup complete [${connections.length} Connection${connections.length > 1 ? 's' : ''}]`)
return true
};
In nock.ts, I have the following content:
import * as nock from 'nock';
const FAKE_ANALYST_USER_CREATED = {
... (some large object)
}
console.log('NOCK URL ' + process.env.AMA_API);
nock(process.env.AMA_API)
.persist()
.log(console.log)
.post('api/analyst/create-analyst-user')
.reply(200, FAKE_ANALYST_USER_CREATED)
That is all the setup for nock I have. Then in the teams.controller.spec.ts, I have the following test:
describe('Team Endpoint', () => {
let connections: Connection[];
beforeAll(async () => {
connections = await initConnection();
});
afterAll(async () => {
await Promise.all(connections.map(con => con.close()));
console.log('connection closed');
return true;
});
describe('Team', () => {
test.only('POST / should return [200 - OK] and create a new team', async () => {
const newTeam = {
...
};
let response = await request(app)
.post('/')
.set('Authorization', adminUserToken())
.send(newTeam);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('name', newTeam.name);
expect(response.body).toHaveProperty('slug', newTeam.slug);
expect(response.body).toHaveProperty('apiKey');
expect(response.body.apiKey).toBeDefined();
response = await request(app)
.delete(`/${response.body.id}`)
.set('Authorization', adminUserToken())
expect(response.status).toBe(200);
});
});
});
Then finally, this test triggers a function in teams.controller.ts with the following content:
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as rp from 'request-promise';
import { config } from 'src/config';
import { Brackets } from 'typeorm/query-builder/Brackets';
import { isUUID } from 'validator';
import { withConnection, withTransaction } from '../../db/connection';
import { Team } from '../../models/team';
/**
* Create a new user
*/
export async function create(req: Request, res: Response) {
const result = await withTransaction(async em => {
const teamRepository = em.getRepository(Team)
... (irrelevant code)
console.log('AMA URL ' + process.env.AMA_API)
const response = await rp({
uri: `${process.env.AMA_API}/api/analyst/create-analyst-user`,
method: 'POST',
json: true,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'Bearer ' + jwt.sign({
id: -1,
role: 'admin',
team: '*',
},
config.secrets.session,
{
expiresIn: 60 * 60
})
},
body: {username: newTeam.name, id: newTeam.id}
});
return response
})
if (result) {
return res.status(201).send(result)
}
}
So with that all out of the way..
All the setup code is reached when running the tests (based on the console output that I see).
However, when I run the tests, the request in the last code block is not intercepted, I get the following output:
Determining test suites to run...[JEST SETUP] setting env var
NOCK URL http://fake.local
Nock init done.
Starting server for profile test...
[Team] Adding initial teams
Connection setup complete [1 Connection]
RUNS src/api/teams/teams.controller.spec.ts
RUNS __tests__/server.spec.ts
Test Suites: 0 of 2 total
Tests: 0 total
Snapshots: 0 total
PASS __tests__/server.spec.ts
● Console console.log __tests__/jest.js:1 [JEST SETUP] setting timeout to 10s
FAIL src/api/teams/teams.controller.spec.ts (6.488s) ● Console
console.log __tests__/jest.js:1
[JEST SETUP] setting timeout to 10s console.log src/config/index.ts:8
Starting server for profile test... console.log src/api/teams/teams.controller.ts:90
AMA URL http://fake.local console.log src/api/teams/teams.controller.spec.ts:16
connection closed
● Team Endpoint › Team › POST / should return [200 - OK] and create a new
team
RequestError: Error: getaddrinfo ENOTFOUND fake.local fake.local:80
at new RequestError (node_modules/request-promise/node_modules/request
-promise-core/lib/errors.js:14:15)
at Request.plumbing.callback (node_modules/request-promise/node_module
s/request-promise-core/lib/plumbing.js:87:29)
at Request.RP$callback [as _callback] (node_modules/request-promise/no
de_modules/request-promise-core/lib/plumbing.js:46:31)
at self.callback (node_modules/request/request.js:185:22)
at Request.Object.<anonymous>.Request.onRequestError (node_modules/req
uest/request.js:881:8)
I have already spent hours trying to find what is going wrong here.. with no success. Any help would be very much appreciated.
In case anyone has the same problem. Moving the nock initialization to beforeall fixed it for me.
This is a really good time to use Nock Recorder. It will record you HTTP calls and let you see exactly why Nock is not matching to the request.
Try this in your teams.controller.ts:
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as rp from 'request-promise';
import { config } from 'src/config';
import { Brackets } from 'typeorm/query-builder/Brackets';
import { isUUID } from 'validator';
import { withConnection, withTransaction } from '../../db/connection';
import { Team } from '../../models/team';
const nock = require('nock') // add Nock
nock.recorder.rec({
output_objects: true,
}) // Setup Nock to Record
/**
* Create a new user
*/
export async function create(req: Request, res: Response) {
const result = await withTransaction(async em => {
const teamRepository = em.getRepository(Team)
... (irrelevant code)
console.log('AMA URL ' + process.env.AMA_API)
const response = await rp({
uri: `${process.env.AMA_API}/api/analyst/create-analyst-user`,
method: 'POST',
json: true,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'Bearer ' + jwt.sign({
id: -1,
role: 'admin',
team: '*',
},
config.secrets.session,
{
expiresIn: 60 * 60
})
},
body: {username: newTeam.name, id: newTeam.id}
});
nock.restore() // stop nock recording
const nockCalls = nock.recorder.play() // "play" the recording into a variable
console.log(`Nock Captured Calls: \n${JSON.stringify(nockCalls,null,2)}`) // inspect calls that Nock recorder
return response
})
if (result) {
return res.status(201).send(result)
}
}

Using serverless-mocha-plugin to test dynamic endpoint

I am creating an API application in NodeJS using the Serverless framework. I have installed the serverless-mocha-plugin and am trying to create some unit tests for my functions.
In my serverless.yml file, I have the following endpoints:
...
equipmentGetAll:
handler: ./api/equipment/equipment.getAll
events:
- http:
path: equipment
method: get
cors: true
equipmentGetOne:
handler: ./api/equipment/equipment.getOne
events:
- http:
path: equipment/{po_number}
method: get
cors: true
...
When testing the getAll endpoint, I use the following test which passes successfully. I have verified it works by logging the response to the console.
'use strict';
// tests for equipmentGetAll
// Generated by serverless-mocha-plugin
const mochaPlugin = require('serverless-mocha-plugin');
const expect = mochaPlugin.chai.expect;
let wrapped = mochaPlugin.getWrapper('equipmentGetAll', '/api/equipment/equipment.js', 'getAll');
describe('equipmentGetAll', () => {
before((done) => {
done();
});
it('should get all Equipment', () => {
return wrapped.run({po_number:117}).then((response) => {
expect(response.statusCode).to.be.equal(200);
expect(response.body.length).to.be.greaterThan(0);
});
});
});
Similarly, for the getOneendpoint, I am (for now) doing a very similar test:
'use strict';
// tests for equipmentGetOne
// Generated by serverless-mocha-plugin
const mochaPlugin = require('serverless-mocha-plugin');
const expect = mochaPlugin.chai.expect;
let wrapped = mochaPlugin.getWrapper('equipmentGetOne', '/api/equipment/equipment.js', 'getOne');
describe('equipmentGetOne', () => {
before((done) => {
done();
});
it('should get one single Equipment', () => {
return wrapped.run({}).then((response) => {
expect(response.statusCode).to.be.equal(200);
expect(response.body.length).to.be.equal(1);
});
});
});
The Problem
The current response I'm receiving for getOne is:
{
statusCode: 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Cannot read property \'po_number\' of undefined'
}
Due to the fact that the path for getOne from serverless.yml is equipment/{po_number} rather than just equipment/.
What is the proper way to pass the path value for the test?
A sample call would hit endpoint my-api-endpoint.com/equipment/117 and return the Equipment with po_number 117. This works properly when testing with POSTMan, but how can I make it work with mocha-serverless-plugin?
To pass data to lambda you should use
wrappedLambda.run({body: "String, not Object"})
To pass queryStringParametr to lambda you should use wrappedLambda.run({queryStringParameters: {a: "first",b:"second"}})
To pass pathParameters to lambda you should use
wrappedLambda.run({pathParameters: {a: "first", b:"second"})
Simple example for testing post method
context('save flashcard', () => {
before((done) => {
done();
});
it('save flashcard successfully', () => {
return saveFlashcard.run({body: correctInput})
.then((response) => {
const body = JSON.parse(response.body);
expect(body).to.have.property('_id')
})
});
});
this body will be located inside event object.
To pass body you need to do something like this
{
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
releaseDate: 2231213213,
title: 'sfsdf',
authorName: 'L'
})
}

Mocking an outgoing request inside an async redux action in a node app doesn't work

I just started building a small app using node and redux by adding on to react-redux-starter-kit.
I am now trying to test an async action which performs an API call, while adhering closely to the redux example.
I am using the package isomorphic-fetch to perform the request and fetch-mock to mock it, but when I run my tests, it performs a real request to my API.
I already noticed that fetch-mock works as expected when I perform the API call right in my it-block, but actually I want to test an imported function that performs the API call.
What do I need to do to get it to work also for an imported function?
This is what my action looks like:
require('es6-promise').polyfill()
const fetch = require('isomorphic-fetch')
export const authenticateUserCredentials = ({email, password}) => {
return (dispatch) => {
return fetch('http://localhost:3005/v1/sign_in', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
}).then(response => response.json())
.then(json => {
console.log('json', json)
})
.catch((reason) => {
console.log('CATCHED ERROR:', reason.name, reason.message)
})
}
}
export const actions = {
authenticateUserCredentials
}
And in my spec file:
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { actions } from 'redux/modules/session'
const fetchMock = require('fetch-mock')
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('(Async Action Creator) Authenticate user credentials', function () {
const EMAIL = 'a#b.de'
const TOKEN = '893irnjkklfnt'
const PASSWORD = 'foobar'
beforeEach(function () {
fetchMock.mock(
'http://localhost:3005/v1/sign_in',
'POST', {
status: 200,
body: '{"email":"' + EMAIL + '","token":"' + TOKEN + '"}'
}
)
})
afterEach(function () {
fetchMock.restore()
})
it('creates SIGN_IN when credentials are valid', (done) => {
const initialState = {}
const expectedActions = [
{ type: AUTHENTICATING, payload: undefined },
{ type: SIGN_IN, payload: {email: EMAIL, token: TOKEN} }
]
const store = mockStore(initialState, expectedActions, () => {
return done()
})
store.dispatch(
actions.authenticateUserCredentials({
email: EMAIL,
password: PASSWORD
})
)
})
})
A better solution is to not assign isomorphic-fetch to a constant as it will already assign itself as a global variable. fetch-mock is designed to work with fetch as a global as that's what the standard says fetch should be. It is possible to get it to work with fetch assigned to some other variable, but it means jumping through unnecessary hoops.
Using this package solves the problem: https://github.com/spaceviewinc/fetch-mock-forwarder.

Resources