I am trying to setup a local testing environment for my firebase cloud functions. However I run into problems when trying to do a fake call to one of my HTTP functions.
The reason for my error seems to be that I am using CORS (npm). When I remove cors and run the function "test" seen below with just a response.status(200) everything works. But when wrapping with cors(req,res) my test fails with TypeError: Cannot read property 'origin' of undefined.
What am I doing wrong here?
In index.js -->
exports.test = functions.https.onRequest((request, response) => {
cors(request, response, () => {
response.status(200);
response.send("test ok");
})
In my test.js
describe('Cloud Functions', () => {
// [START stubConfig]
var myFunctions, configStub, adminInitStub, functions, admin, cors;
before(() => {
// Since index.js makes calls to functions.config and admin.initializeApp at the top of the file,
// we need to stub both of these functions before requiring index.js. This is because the
// functions will be executed as a part of the require process.
// Here we stub admin.initializeApp to be a dummy function that doesn't do anything.
admin = require('firebase-admin');
cors = require('cors')({
origin: true
});
adminInitStub = sinon.stub(admin, 'initializeApp');
// Next we stub functions.config(). Normally config values are loaded from Cloud Runtime Config;
// here we'll just provide some fake values for firebase.databaseURL and firebase.storageBucket
// so that an error is not thrown during admin.initializeApp's parameter check
functions = require('firebase-functions');
configStub = sinon.stub(functions, 'config').returns({
firebase: {
databaseURL: 'https://not-a-project.firebaseio.com',
storageBucket: 'not-a-project.appspot.com',
}
// You can stub any other config values needed by your functions here, for example:
// foo: 'bar'
});
// Now we can require index.js and save the exports inside a namespace called myFunctions.
// This includes our cloud functions, which can now be accessed at myFunctions.makeUppercase
// and myFunctions.addMessage
myFunctions = require('../index');
});
after(() => {
// Restoring our stubs to the original methods.
configStub.restore();
adminInitStub.restore();
});
// [END stubConfig]
describe('test', () => {
it('should return status code 200', (done) => {
// [START invokeHTTPS]
// A fake request object, with req.query.text set to 'input'
const req = {};
// A fake response object, with a stubbed redirect function which asserts that it is called
// with parameters 303, 'new_ref'.
const res = {
status: (status) => {
assert.equal(status, 200);
done();
}
};
// Invoke addMessage with our fake request and response objects. This will cause the
// assertions in the response object to be evaluated.
myFunctions.test(req, res);
// [END invokeHTTPS]
})
})
})
Try this:
Instead of using const req = {}; use:
const req = {
headers: { origin: true },
};
Here's how I got around the cors errors, with sinon
const res = {};
Object.assign(res, {
status: sinon.stub().returns(res),
end: sinon.stub().returns(res),
json: sinon.stub().returns(res),
setHeader: sinon.stub(),
getHeader: sinon.stub(),
});
beforeEach(() => {
Object.values(res).forEach(stub => stub.resetHistory());
});
Then, in your test, you can test your responses:
cloudFunctions.testFunction({ query: { text: 'foo' } }, res);
response = res.json.lastCall.args[0];
Finaly the unit test with firebase function and cors is working
You have to setHeader and GetHeader in the response
setHeader: (key, value) => {},
getHeader: (value) => {},
The test
const test = require('firebase-functions-test')()
let myFunctions
beforeAll(() => {
myFunctions = require('../src/index')
})
afterAll(() => {
test.cleanup()
})
it('get', async () => {
let request = { headers: { origins: true } }
const response = {
setHeader: (key, value) => {
},
getHeader: (value) => {
},
status: (code) => {
expect(code).toBe(200)
return { send: (body) => {
expect(body).toBe('ok')
}
}
}
}
myFunctions.addLink(request, response)
})
The Index
const functions = require('firebase-functions')
const cors = require('cors')({
origin: true
})
exports.addLink = functions.https.onRequest((req, res) => {
return cors(req, res, () => {
res.status(200).send('ok')
})
})
add headers: { origin: '*' }, to your request and setHeader () {} to response
const req = {
{ origin: '*' }
};
const res = {
status: (status) => {
assert.equal(status, 200);
done();
}
};
Related
I have following Node.js script.
const axios = require('axios');
const https = require('https');
const postRequest = (url, data) => {
gLogger.debug('postRequest started');
// try {
const headers = {
'Content-Type': 'application/json',
'Content-Length': JSON.stringify(data).length,
};
const load = {
headers,
httpsAgent: agent,
};
gLogger.debug(`postRequest load: ${JSON.stringify(load)}`);
const result = axios.post(url, data, load).then((response) => {
return result;
})
.catch((error) => {
return error;
});
};
And this is for unit test:
const axios = require('axios');
const personalRecords = {
data: { peopleDomain: { paom: { data: { persons: [], delta: 1, recordsFetched: 10 } } } },
};
const tockenData = {
data: {
access_token: 'access_token',
expires_in: 1000,
},
};
// jest.useFakeTimers();
jest.setTimeout(8000);
jest.mock('axios', () => ({
post: jest.fn().mockReturnValue(tockenData),
get: jest.fn().mockReturnValue(personalRecords),
defaults: jest.fn().mockReturnValue(),
}));
The problem when I am running unit test yarn test, I keep getting the following error:
TypeError: axios.post(...).then is not a function.
What is the problem and how to fix it?
This is because you mock post function to be a function that returns a value instead of a promise. Remember post returns promise
This is the line that causes trouble:
post: jest.fn().mockReturnValue(tockenData),
To mock axios, there is an answer here:
How do I test axios in Jest?
I'm trying to call Redis from a Twilio Function (serverless) and I don't see incoming connections in my Redis log.
Is this setup viable?
Sample code follows:
const Redis = require('ioredis');
const fs = require('fs');
exports.handler = function (context, event, callback) {
const config = Runtime.getAssets()['config.json'].open();
let redisClientConfig = JSON.parse(config).redisConfig;
let contactCacheTime = JSON.parse(config).contactCacheTime;
if (!redisClientConfig) {
throw new Error('Redis config not set.');
}
const redisClient = new Redis(redisClientConfig);
redisClient.on('error', (err) => {
console.error(`Cannot connect to redis, reason: ${(err.message || err)}`);
});
redisClient.getex('mhn-twilio-bot-contact:'.concat(event.contactKey), 'EX', contactCacheTime)
.then((res) => {
if (!res) {
redisClient.setex('mhn-twilio-bot-contact:'.concat(event.contactKey), contactCacheTime, '<CACHED-VALUE>');
}
callback(null, { cached: res ? true : false });
})
.catch((err) => {
callback(null, { cached: false });
});
};
I have 2 Firebase functions that I want to execute when there is an Http request, one function (createEmailList) to save data in the Firebase database, the other (zohoCrmHook) to to save in a 3rd party CRM called Zoho.
When the functions are deployed to Firebase, the functions log shows that both are properly deployed. However, when the Http request is made from the frontend, the log shows that only one of the functions (createEmailList) is being executed.
As the log shows, the first function createEmailList is being executed and the data shows up in the Firebase database with no problem. However, The second function zohoCrmHook is not even being executed.
index.js
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const serviceAccount = require("./service_account.json");
const createEmailList = require('./createEmailList')
// zoho
const zohoCrmHook = require('./zohoCrmHook')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://landing-page.firebaseio.com"
})
exports.zohoCrmHook = functions.https.onRequest(zohoCrmHook)
exports.createEmailList = functions.https.onRequest(createEmailList)
createEmailList.js
const admin = require('firebase-admin')
const cors = require('cors')({ origin: true })
module.exports = (req, res) => {
cors(req, res, () => {
if (!req.body.email) {
return res.status(422).send({ error: 'Bad Input'})
}
const email = String(req.body.email)
const firstName = String(req.body.firstName)
const lastName = String(req.body.lastName)
const data = {
email,
firstName,
lastName
}
const db = admin.firestore()
const docRef = db.collection('users')
.doc(email)
.set(data, { merge: false })
.catch(err => res.status(422).send({ error: err }))
res.status(204).end();
})
}
zohoCrmHook.js
const axios = require('axios');
const functions = require('firebase-functions');
// zoho
const clientId = functions.config().zoho.client_id;
const clientSecret = functions.config().zoho.client_secret;
const refreshToken = functions.config().zoho.refresh_token;
const baseURL = 'https://accounts.zoho.com';
module.exports = async (req, res) => {
const newLead = {
'data': [
{
'Email': req.body.email,
'Last_Name': req.body.lastName,
'First_Name': req.body.firstName,
}
],
'trigger': [
'approval',
'workflow',
'blueprint'
]
};
const { data } = await getAccessToken();
const accessToken = data.access_token;
const leads = await getLeads(accessToken);
const result = checkLeads(leads.data.data, newLead.data[0].Email);
if (result.length < 1) {
try {
return res.json(await createLead(accessToken, newLead));
}
catch (e) {
console.log(e);
}
}
else res.json({ message: 'Lead already in CRM' })
}
function getAccessToken () {
const url = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=refresh_token`;
return new Promise((resolve, reject) => {
axios.post(url)
.then((response) => {
return resolve(response);
})
.catch(e => console.log(e))
});
}
function getLeads(token) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
axios.get(url, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
return resolve(response);
})
.catch(e => console.log(e))
})
}
function createLead(token, lead) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
const data = JSON.stringify(lead);
axios.post(url, data, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
console.log(response.data)
return resolve(response);
})
.catch(e => reject(e))
})
}
function checkLeads(leads, currentLead) {
return leads.filter(lead => lead.Email === currentLead)
}
Since you're exporting two functions.https.onRequest declarations, you'll end up with two Cloud Functions, each with their own URL/endpoint. So if that's what you need, you'll need to configure two web hooks that call these functions.
From reading your question however, it sounds more like you want a single Cloud Function that does two things, in which case you should only have one functions.https.onRequest declaration that then calls two regular JavaScript functions (for example).
So something more like:
exports.myWebHook = functions.https.onRequest(function(req, res) {
zohoCrmHook(...);
createEmailList(...);
})
You'll need to figure out what to pass into the two function calls here, as you can't pass the request and response along.
Alternatively you can call the two Cloud Functions from here, but that typically just drives up your cost with little benefit.
I am currently working with azure functions in javascript. In my function, I am first getting a specific element from my CosmoDB (this is the async/await part). I get a result and then I want to do an https POST request. However, my problem is, that it never finished the HTTPs request and I don't really know why. What am I doing wrong?
(As you can see I tried 2 different ways of doing the request, once with the standard https function and the commented out the part with npm request package. However, both ways won't work).
Here is my code:
const CosmosClient = require('#azure/cosmos').CosmosClient;
var https = require('https');
var http = require('http');
var request = require('request');
const endpoint = "someEndpoint";
const masterKey = "anymasterkey";
const database = {
"id": "Database"
};
const container = {
"id": "Container1"
};
const databaseId = database.id;
const containerId = container.id;
const client = new CosmosClient({
endpoint: endpoint,
auth: {
masterKey: masterKey
}
});
module.exports = function (context, req) {
const country = "de";
const bban = 12345678;
const querySpec = {
query: "SELECT * FROM Container1 f WHERE f.country = #country AND f.bban = #bban",
parameters: [{
name: "#country",
value: country
},
{
name: "#bban",
value: bban
}
]
};
getContainers(querySpec).then((results) => {
const result = results[0];
context.log('here before request');
var options = {
host: 'example.com',
port: '80',
path: '/test',
method: 'POST'
};
// Set up the request
var req = http.request(options, (res) => {
var body = "";
context.log('request');
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
context.res = body;
context.done();
});
}).on("error", (error) => {
context.log('error');
context.res = {
status: 500,
body: error
};
context.done();
});
req.end();
// request({
// baseUrl: 'someURL',
// port: 443,
// uri: 'someuri',
// method: 'POST',
// headers: {
// 'Content-Type': 'text/xml;charset=UTF-8',
// 'SOAPAction': 'someaction'
// },
// function (error, response, body) {
// context.log('inside request')
// if (error) {
// context.log('error', error);
// } else {
// context.log('response');
// }
// }
// })
})
};
async function getContainers(querySpec) {
const {container, database} = await init();
return new Promise(async (resolve, reject) => {
const {
result: results
} = await container.items.query(querySpec).toArray();
resolve(results);
})
}
async function init() {
const {
database
} = await client.databases.createIfNotExists({
id: databaseId
});
const {
container
} = await database.containers.createIfNotExists({
id: containerId
});
return {
database,
container
};
}
The last thing that happens is the print of "here before request". After that the function just does nothing until it timesout. So what am I doing wrong? Can't I just this combination of await/async and requests?
As commented you are not sending any data to the POST call. You need to have a req.write before the req.end
req.write(data);
req.end();
That is why the POST call is failing for you. After this fix, it should work
I'm trying to unit test a restify route that returns an S3 object from a bucket
my route is:
module.exports = function(server) {
server.get('/configs/:version', (req, res, next) => {
const s3 = new AWS.S3();
const params = {
Bucket: 'testBucket',
Key: 'testKey'
};
function send(data, next) {
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Cache-Control', 'no-cache');
res.status(200);
res.send(data.Body);
next();
}
s3.getObject(params, (err, data) => (err) ? next(err) : send(data, next));
});
};
For my unit test I've been trying to mock the S3 constructor so I can stub getObject and failing miserably.
describe('#configs', () => {
let req;
let res;
let next;
let server;
let config;
let AWS;
let S3;
let route;
beforeEach(() => {
req = {
params: {
version: 'testVersion'
}
};
res = {
send: sinon.spy(),
};
next = sinon.spy();
server = {
get: sinon.stub(),
};
config = {
get: sinon.stub(),
}
AWS = () => {
return {
S3: () => {
return {
getObject: sinon.stub()
}
}
}
}
route = proxyquire(process.cwd() + '/lib/routes/configs/get', {
'configs.js': config,
'aws-sdk': AWS,
});
route(server);
});
describe('#GET', () => {
it('Should register configs get route', () => {
let s3 = sinon.createStubInstance(AWS.S3, {
getObject: sinon.stub(),
});
server.get.callArgWith(1, req, res, next);
expect(server.get).calledOnce.calledWith('/configs/:version');
expect(s3.getObject).calledOnce.calledWith({
Bucket: 'testBucket',
Key: 'testKey'
});
});
});
});
But I getting this error:
TypeError: undefined is not a spy or a call to a spy! on the getObject method.
After reading sinon docs over and over again I can't understand how to mock the constructor, how can I stub the getObject method so I can make sure it's being called correctly and it's returns so I know it's responses are being treated correctly Can someone help me with this?
Finally made my mocks work, the issue was that I was mocking AWS as a function not has an object, it's S3 that needs to be mocked as a function, because it's S3 that needs to be instantiated. This is how the mock should look like:
function S3() {
return s3;
}
s3 = {
getObject: sinon.stub(),
putObject: sinon.stub()
};
AWS = {
config: {
update: sinon.stub()
},
S3: S3
};
Like this if one needs to mock putObject, he just needs for example do this:
s3.putObject.callsArgWith(1, err, data);
const getObjectStub = AWS.S3.prototype.getObject = Sinon.stub();
getObjectStub.yields(null, {
AcceptRanges: "bytes",
ContentLength: 3191,
ContentType: "image/jpeg",
Metadata: {
},
TagCount: 2,
VersionId: "null"
}
);