Firebase stub transaction - firebase

I am trying to stub a transaction over real time database table.The transaction is inside a function called by a trigger (non HTTP). I can fire the trigger, but I'm not able to stub transaction like this:
var codeRef = admin.database().ref('last_code')
return codeRef.transaction(function (currentCode) {
return currentCode + 1
})
.then(result => {
const {error, committed, snapshot} = result
return snapshot.val()
})
I am using stub Sinon with mocha Unit testing of Cloud Functions. This is the way I tried:
const test = require('firebase-functions-test')();
adminInitStub = sinon.stub(admin, 'initializeApp');
// Now we can require index.js and save the exports inside a namespace called myFunctions.
myFunctions = require('../index');
const refParam = 'last_code';
const databaseStub = sinon.stub();
const refStub = sinon.stub();
const transactionStub = sinon.stub();
Object.defineProperty(admin, 'database', { get: () => databaseStub });
databaseStub.returns({ ref: refStub });
refStub.withArgs(refParam).returns({transaction: function(code) => ({committed: true, snapshot: 999});
But stub transaction fails. I am sure that last line is incorrect, but don't see the solution.

Finally I resolve in this way:
const obj = {
a: (() => function(code){
return 999
})};
let dbSnap = {
val: function() {
return 999;
}
};
var resolveStub = sinon.stub(obj,"a");
const result = {"error": null, "committed" : true, "snapshot" : dbSnap};
resolveStub.resolves(result);
refStub.withArgs("last_booking_code").returns({transaction: resolveStub});

Related

AWS Lambda: Async Calls outside handler (initialization section, invoke lambda)

I would like to call an asynchronous function outside the lambda handler with by the following code:
var client;
(async () => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
});
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
The 'client' does not get initialized. The same code works perfectly if executed within the handler.
initSecrets contains a lambda invocation of getSecrets() which calls the AWS SecretsManager
Has anyone an idea how asynchronous functions can be properly called for initialization purpose outside the handler?
Thank you very much for your support.
I ran into a similar issue trying to get next-js to work with aws-serverless-express.
I fixed it by doing the below (using typescript so just ignore the :any type bits)
const appModule = require('./App');
let server: any = undefined;
appModule.then((expressApp: any) => {
server = createServer(expressApp, null, binaryMimeTypes);
});
function waitForServer(event: any, context: any){
setImmediate(() => {
if(!server){
waitForServer(event, context);
}else{
proxy(server, event, context);
}
});
}
exports.handler = (event: any, context: any) => {
if(server){
proxy(server, event, context);
}else{
waitForServer(event, context);
}
}
So for your code maybe something like
var client = undefined;
initSecrets("MyWebApi").then(result => {
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET)
})
function waitForClient(){
setImmediate(() => {
if(!client ){
waitForClient();
}else{
client('Request')
}
});
}
exports.handler = async function (event, context) {
if(client){
client('Request')
}else{
waitForClient(event, context);
}
};
client is being called before it has initialised; the client var is being "exported" (and called) before the async function would have completed. When you are calling await client() the client would still be undefined.
edit, try something like this
var client = async which => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
let api = new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
return api(which) // assuming api class is returning a promise
}
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
This can be also be solved with async/await give Node v8+
You can load your configuration in a module like so...
const fetch = require('node-fetch');
module.exports = async () => {
const config = await fetch('https://cdn.jsdelivr.net/gh/GEOLYTIX/public/z2.json');
return await config.json();
}
Then declare a _config outside the handler by require / executing the config module. Your handler must be an async function. _config will be a promise at first which you must await to resolve into the configuration object.
const _config = require('./config')();
module.exports = async (req, res) => {
const config = await _config;
res.send(config);
}
Ideally you want your initialization code to run during the initialization phase and not the invocation phase of the lambda to minimize cold start times. Synchronous code at module level runs at initialization time and AWS recently added top level await support in node14 and newer lambdas: https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/ . Using this you can make the init phase wait for your async initialization code by using top level await like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
console.log("start init");
await sleep(1000);
console.log("end init");
export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
This works great if you are using ES modules. If for some reason you are stuck using commonjs (e.g. because your tooling like jest or ts-node doesn't yet fully support ES modules) then you can make your commonjs module look like an es module by making it export a Promise that waits on your initialization rather than exporting an object. Like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const main = async () => {
console.log("start init");
await sleep(1000);
console.log("end init");
const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
return { handler };
};
# note we aren't exporting main here, but rather the result
# of calling main() which is a promise resolving to {handler}:
module.exports = main();

How to subscribe a firebase function so that it executes via a pub/sub trigger

I have been asked to create a firebase (fb) function that triggers onUpdate. Then I have to gather a bunch of information from the fb database and publish a message so that another function triggers at that point.
fb update triggers functionA
functionA publishes a message
functionB is a subscriber to that topic and is triggered after the message publishes.
I have the basics of the onUpdate trigger below:
const functions = require("firebase-functions"),
Promise = require("promise"),
PubSub = require(`#google-cloud/pubsub`),
admin = require("firebase-admin");
const pubsub = new PubSub();
exports.checkInOrder = functions.database
.ref("/orders/{id}")
.onUpdate((change, context) => {
const after = change.after.val();
// check the status: "pending-pickup" or "fulfilled" TODO
if (after.status === "new") {
console.log("ended because package is new.");
return null;
}
let dsObj = {};
const orderId = context.params.id;
const topicName = 'check-in-order';
const subscriptionName = 'check-in-order';
return // how would I send the message to the pubsub here?
});
So to summarize:
How do I send a message to pubsub
How do I subscribe a firebase function to trigger when a topic receives a message?
If it sounds very confusing I'm sorry - I am completely lost here. Thanks!
So I finally figured it out. Pretty straight forward, just felt overwhelmed with learning all of this stuff at once and in a rushed time frame. Below is my code. I included the whole page so in case this might help someone else out there in the future.
const functions = require("firebase-functions"),
Promise = require("promise"),
PubSub = require(`#google-cloud/pubsub`),
admin = require("firebase-admin");
const init = () => {
const topicName = "check-in-order";
pubsub
.createTopic(topicName)
.then(results => {
const topic = results[0];
console.log(`Topic ${topicName} created.`);
return;
})
.catch(err => {
console.error("ERROR on init:", err);
return;
});
};
const pubsub = new PubSub();
exports.orderCreated = functions.database
.ref("/orders/{id}")
.onUpdate((change, context) => {
console.log("it's happening!");
const after = change.after.val();
console.log("after::::>", after)
if (after.status === "new") {
console.log('stopped because status is new');
return null;
}
if (after.status === "pending-pickup" || after.status === "fulfilled") {
const orderId = context.params.id;
const topicName = "check-in-order";
let { assignedSlot, userId, parcelLockerId, carrier, trackingNumber, orderInDate, pickUpCode, status, } = after;
const dsObj = {
order: {
orderId,
userId,
parcelLockerId,
carrier,
trackingNumber,
orderInDate,
pickUpCode,
status,
}
};
const dataBuffer = new Buffer.from(dsObj.toString());
// publish to trigger check in function
return pubsub
.topic(topicName)
.publisher()
.publish(dataBuffer)
.then(messageId => {
console.log(`:::::::: Message ${messageId} has now published. :::::::::::`);
return true;
})
.catch(err => {
console.error("ERROR:", err);
throw err;
});
}
return false;
});
exports.checkInOrder = () => {
}
exports.checkIn = functions.pubsub.topic('check-in-order').onPublish((message) => {
console.log("everything is running now", message);
return true;
});
init();

what should I do If I want to do nothing in the one of my execution path in Background trigger cloud function?

as far as I know, background trigger cloud function should return a promise,right? but what if I want to do nothing in the one of my execution path ?
export const updateDataWhenUserUnattendTheEvent = functions.firestore
.document('events/{eventId}/Attendee/{userId}')
.onDelete((snap, context) => {
const eventID = context.params.eventId
const eventRef = snap.ref.firestore.collection('events').doc(eventID)
const db = admin.firestore()
return db.runTransaction(async t => {
const doc = await t.get(eventRef)
if (doc) {
const eventRankPoint = doc.data().rankPoint
let eventCapacity = doc.data().capacity
return t.update(eventRef,{
isFullCapacity : false,
capacity : eventCapacity + 1,
rankPoint: eventRankPoint - 1
})
} else {
// what should I write in here? empty promise?
return new Promise()
}
})
})
I want to my function worked only if the document is exist. so what should I do ? I write new Promise but .... I don't know what to do actually. thanks in advance
You can just return null if there's no asynchronous work to perform in some code path of your functions. You only truly need a promise if it tracks some async work.
Alternatively, you could return a promise that's resolved immediately with Promise.resolve(null)
Because db.runTransaction is an async function it will return a Promise all the time.
You can drop the else statement and the method will perform as expected because runTransaction will return Promise<void> which is a valid response for Cloud Functions
export const updateDataWhenUserUnattendTheEvent = functions.firestore
.document('events/{eventId}/Attendee/{userId}')
.onDelete((snap, context) => {
const eventID = context.params.eventId;
const eventRef = snap.ref.firestore.collection('events').doc(eventID);
const db = admin.firestore();
return db.runTransaction(async t => {
const doc = await t.get(eventRef);
if (doc) {
const eventRankPoint = doc.data().rankPoint;
let eventCapacity = doc.data().capacity ;
return t.update(eventRef,{
isFullCapacity : false,
capacity : eventCapacity + 1,
rankPoint: eventRankPoint - 1
});
}
});
});
You can also make the onDelete function async which means you can force it to always return a Promise - the below is valid and will exit the function correctly.
export const updateDataWhenUserUnattendTheEvent = functions.firestore
.document('events/{eventId}/Attendee/{userId}')
.onDelete(async (snap, context) => {
// Do Nothing
return;
});

How to Count Users with a Firebase Cloud Function (getting Function Returned Undefined error)

I have a Firebase Cloud Function that assigns a number to a user on onWrite. The following code works but something is wrong because the console logs state Function returned undefined, expected Promise or value.
I'm also not sure how to refer to the root from inside the onWrite so I've created several "parent" entries that refer to each other. I'm sure there is a better way.
onWrite triggers on this:
/users/{uid}/username
The trigger counts the children in /usernumbers and then writes an entry here with the uid and the child count + 1:
/usernumbers/uoNEKjUDikJlkpLm6n0IPm7x8Zf1 : 5
Cloud Function:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
parent3usernumbers.once("value")
.then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
return console.log('User Number Written', uid, a);
});
});
});
Is there a better way to do this? How can I get the Function Returned Undefined error to go away?
I should also mention it takes a few seconds for the 'usernumber' entry to be written. I'm guessing it's waiting for the function to return something.
Your function have to return a Promise :
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
return new Promise((resolve, reject) => {
parent3usernumbers.once("value").then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
console.log('User Number Written', uid, a);
resolve({uid : uid, a : a})
}).catch(function(e) {
reject(e)
})
});
});
});

Firebase Deploy Error: Failed to configure trigger

I have following sample function from this tutorial: Asynchronous Programming (I Promise!) with Cloud Functions for Firebase - Firecasts
exports.emailEmployeeReport = functions.database
.ref('/employees/${eid}/reports/${rid}')
.onWrite(event => {
const eid = event.params.eid;
const report = event.data.val().report;
const root = event.data.ref.root;
const mgr_promise = root.child(`/employees/${eid}/manager`).once('value');
const then_promise = mgr_promise.then(snap => {
const mgr_id = snap.val();
const email_promise = root.child(`/employees/${mgr_id}/email`).once('value');
return email_promise;
}).catch(reason => {
// Handle the error
console.log(reason);
});;
const then_promise2 = then_promise.then(snap => {
const email = snap.val();
const emailReportPromise = sendReportEmail(email, report);
return emailReportPromise;
}).catch(reason => {
// Handle the error
console.log(reason);
});
return then_promise2;
});
var sendReportEmail = function (email, report) {
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
setTimeout(function () {
try {
var someValue = "sendReportEmail";
console.log(someValue);
// fulfilled
resolve(someValue);
}
catch (ex) {
// rejected
reject(ex);
}
}, 2000);
});
return myFirstPromise;
}
once I run firebase deploy command, eventually I am getting following error:
functions[emailEmployeeReport]: Deploy Error: Failed to configure
trigger
providers/google.firebase.database/eventTypes/ref.write#firebaseio.com
(emailEmployeeReport)
I also have a simple hello-world method and a similar trigger method, and they deploy fine.
Am I missing something here?
The syntax for wildcards in the database reference does not have "$".
Try the following:
exports.emailEmployeeReport = functions.database
.ref('/employees/{eid}/reports/{rid}')

Resources