Test firestore trigger locally - javascript

I am writing a test which tests a firebase trigger. The problem, however, is that I cannot make it work.
I want to use the local firestore emulator and Jest in order to simulate a change in the firestore and see if the trigger does what it needs to do.
I require the cloud function in my test and I initialize my app
Setup.js:
const firebase = require('#firebase/testing');
const PROJECT_ID = 'project';
let admin;
let db;
const setupAdmin = async () => {
admin = firebase.initializeAdminApp({
projectId: PROJECT_ID
});
db = admin.firestore();
};
const getAdmin = () => {
return admin;
};
const getDb = () => {
return db;
};
module.exports.setupAdmin = setupAdmin;
module.exports.getAdmin = getAdmin;
module.exports.getDb = getDb;
Test.js
describe('Billing', () => {
let dbRef;
beforeAll(async () => {
const {db, admin} = require('../../../functions/helpers/setup');
dbRef = db;
});
afterAll(async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
console.log(`View rule coverage information at ${COVERAGE_URL}\n`);
});
it('test', async () => {
const mockData = {
'Users/user1': {
uid: 'user1'
},
['Users/user1/Taxes/' + new Date().getFullYear().toString()]: {
totalExpenseEuro: 0
}
};
for (const key in mockData) {
const ref = dbRef.doc(key);
await ref.set(mockData[key]);
}
// Create mockup data
await dbRef.collection('Users').doc('user1').collection('Expenses').doc('expense1').set({
amountEuroInclVAT: 100
});
// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({amountEuroInclVAT: 0}, 'Users/user1/Expenses/expense1');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot(
{amountEuroInclVAT: 100},
'Users/user1/Expenses/expense1'
);
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(calculateTaxesOnExpenseUpdate);
wrapped(change, {
params: {
uid: 'test1'
}
});
});
});
Now the main problem comes when I try to access this db object in my trigger
const calculateTaxesOnExpenseUpdate = functions.firestore
.document('Users/{uid}/Expenses/{expenseId}')
.onWrite(async (change, context) => {
const {getDb} = require('../helpers/setup'); // This setup is the same as above
let db = getDb();
...
For some reason when I perform an action like (await db.collection('Users').get()).get('totalExpenseEuro'), Jest stops executing my code. When I set a debugger right after that line, it never gets printed. That piece of code crashes, and I have no idea why. I think the DB instance if not properly configured in my cloud trigger function.
Question: What is a good way of sharing the DB instance (admin.firestore()) between the test and the cloud trigger functions?

Related

Testing with third party dependencies

if in js I have this function (don't focus on what this codex does):
export const user = () => {
const user = ref(null)
const load = async () => {
const _user = await getCurrentUser()
const userRef = doc(collection(db, 'users'), _user?.uid)
await useDocument(userRef, { target: user }).promise.value
}
return {
user,
load
}
})
how do i test user.load()? This function contains dependencies like getCurrentUser() and useFireStore() which must be configured before calling the user function itself.
Basically, how do I manage these external dependencies? i know i should use mocks but i don't understand how they work.

How can I access Firestore data from within a Google Cloud Function?

This is my first time using Cloud Functions. I'm trying to make a simple call to access all the businesses stored in my Firestore collection, but when I try to log the results, I always get an empty array.
All things w/ Firebase/store are set up properly, collection name is listed properly, and have confirmed access to the database by logging db. Is there something obviously wrong with my code here? Thanks!
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
exports.updateBusinessData = functions.https.onRequest((request, response) => {
const db = admin.firestore()
const businessesReference = db.collection('businesses')
var businesses = []
const getBusinesses = async () => {
const businessData = await businessesReference.get()
businesses = [...businessData.docs.map(doc => ({...doc.data()}))]
for (let business in businesses) {
console.log(business)
}
response.send("Businesses Updated")
}
getBusinesses()
});
I tweaked the way you were processing the docs and I'm getting proper data from firestore.
exports.updateBusinessData = functions.https.onRequest((request, response) => {
const db = admin.firestore();
const businessesReference = db.collection("businesses");
const businesses = [];
const getBusinesses = async () => {
const businessData = await businessesReference.get();
businessData.forEach((item)=> {
businesses.push({
id: item.id,
...item.data(),
});
});
// used for of instead of in
for (const business of businesses) {
console.log(business);
}
response.send(businesses);
};
getBusinesses();
});
Your getBusinesses function is async: you then need to call it with await. Then, since you use await in the Cloud Function you need to declare it async.
The following should do the trick (untested):
exports.updateBusinessData = functions.https.onRequest(async (request, response) => {
try {
const db = admin.firestore()
const businessesReference = db.collection('businesses')
var businesses = []
const getBusinesses = async () => {
const businessData = await businessesReference.get()
businesses = businessData.docs.map(doc => doc.data());
for (let business in businesses) {
console.log(business)
}
}
await getBusinesses();
// Send back the response only when all the asynchronous
// work is done => This is why we use await above
response.send("Businesses Updated")
} catch (error) {
response.status(500).send(error);
}
});
You are probably going to update docs in the for (let business in businesses) loop to use await. Change it to a for … of loop instead as follows:
for (const business of businesses) {
await db.collection('businesses').doc(...business...).update(...);
}
Update following the comments
Can you try with this one and share what you get from the console.logs?
exports.updateBusinessData = functions.https.onRequest(async (request, response) => {
try {
console.log("Function started");
const db = admin.firestore()
const businessesReference = db.collection('businesses');
const businessData = await businessesReference.get();
console.log("Snapshot size = " + businessData.size);
const businesses = businessData.docs.map(doc => doc.data());
for (const business of businesses) {
console.log(business);
}
response.send("Businesses Updated")
} catch (error) {
console.log(error);
response.status(500).send(error);
}
});

Node.Js google dataflow sinon stub is not working

Here is my function which calls google dataflow function
index.js
const { google } = require('googleapis');
const triggerDataflowJob = async (event, context) => {
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
});
const authClient = await auth.getClient();
const projectId = await auth.getProjectId();
const dataflow = google.dataflow({ version: 'v1b3', auth: authClient });
const dataflowReqBody = dataflowRequest(projectId, event.bucket, event.name, context);
return dataflow.projects.locations.templates.create(dataflowReqBody);
};
module.exports = { triggerDataflowJob };
My unit test for above function
index.test.js
const { google } = require('googleapis');
const { triggerDataflowJob } = require('./index.js');
describe('Function: triggerDataflowJob', () => {
it('should return success', async () => {
const projectsStub = sinon.stub().returnsThis();
const locationsStub = sinon.stub().returnsThis();
const dataflowStub = sinon.stub(google, 'dataflow').callsFake(() => ({
projects: projectsStub,
locations: locationsStub,
templates: sinon.stub(),
}));
const context = { eventId: '126348454' };
const event = { bucket: 'test-bucket', name: 'test-file.json' };
await triggerDataflowJob(event, context);
sinon.assert.calledOnce(dataflowStub);
});
});
But I am getting below error when I run test.
1) Trigger Dataflow Job:
Function: triggerDataflowJob
should return success:
TypeError: Cannot read property 'templates' of undefined
at triggerDataflowJob (index.js:12:38)
at process._tickCallback (internal/process/next_tick.js:68:7)
Can some one please help where is the issue? or what I am missing or doing wrong?
From the error, it looks like the dataflow object you are getting back does not have a templates key within the location object.
Looking at your test, it looks like the dataflow object looks something like this:
const dataflow = {
projects: {...},
locations: {...},
templates: {...}
}
In the return statement of the main function you are looking for templates within locations.
return dataflow.projects.locations.templates.create(dataflowReqBody);
If locations is suppose to have a templates key, then you may need to update your test and the way your mocking the object. If it is not suppose to have a templates key then you can update your return statement like so:
return dataflow.projects.templates.create(dataflowReqBody);
Hope that helps!

Firebase Multi path atomic update with child values?

I am succesfully updating my user's profile picture on their profile and on all of their reviews posted with this function:
export const storeUserProfileImage = (url) => {
const { currentUser } = firebase.auth();
firebase.database().ref(`/users/${currentUser.uid}/profilePic`)
.update({ url });
firebase.database().ref('reviews')
.orderByChild('username')
.equalTo('User3')
.once('value', (snapshot) => {
snapshot.forEach((child) => {
child.ref.update({ profilePic: url });
});
});
};
I am aware that I should be using an atomic update to do this so the data updates at the same time (in case a user leaves the app or something else goes wrong). I am confused on how I can accomplish this when querying over child values.
Any help or guidance would be greatly appreciated!
Declare a variable to store all the updates. Add the updates as you read them on your listener's loop. When the loop is finished, run the atomic update.
export const storeUserProfileImage = (url) => {
const { currentUser } = firebase.auth();
firebase.database().ref('reviews')
.orderByChild('username')
.equalTo('User3')
.once('value', (snapshot) => {
var updates = {};
updates[`/users/${currentUser.uid}/profilePic`] = url;
snapshot.forEach((child) => {
updates[`/reviews/${child.key}/profilePic`] = url;
});
firebase.database().ref().update(updates);
});
};

Delete item after a date (Firebase Cloud function)

I'm trying to write a firebase cloud function to delete automatically an event after is date is passed.
Based on this example Firebase example, I came to this, but when I'm uploading it on Firebase, it is running on Firebase side but it is not deleting events.
Do you guys have advices or see something wrong in my code ? Is it possible that the problem may coming from the trigger onWrite() ?
/* My database structure
/events
item1: {
MyTimestamp: 1497911193083
},
item2: {
MyTimestamp: 1597911193083
}
...
*/
// Cloud function to delete events after the date is passed
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.deleteOldItems = functions.database.ref('/events/{eventId}').onWrite((change) => {
const ref = change.after.ref.parent; // reference to the parent
const now = Date.now();
const oldItemsQuery = ref.orderByChild('MyTimestamp').endAt(now);
return oldItemsQuery.once('value').then((snapshot) => {
// create a map with all children that need to be removed
const updates = {};
snapshot.forEach(child => {
updates[child.key] = null;
});
return ref.update(updates);
// execute all updates in one go and return the result to end the function
});
});
There is nothing wrong with the code, just update you cloud functions & admin:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.deleteOldItems = functions.database.ref("/events/{eventId}").onWrite((change, context) => {
if (change.after.exists() && !change.before.exists()) {
const ref = change.after.ref.parent;
const now = Date.now();
const oldItemsQuery = ref.orderByChild('MyTimestamp').endAt(now);
return oldItemsQuery.once('value').then((snapshot) => {
const updates = {};
snapshot.forEach(child => {
updates[child.key] = null;
});
return ref.update(updates);
});
} else {
return null;
}
});
Run the following in the functions folder:
npm install firebase-functions#latest --save
npm install firebase-admin#5.11.0 --save
Reference here for more details
try to change
admin.initializeApp();
to :
admin.initializeApp(functions.config().firebase);

Categories

Resources