Firebase cloud function local code changes are not reflected in emulators - node.js

I am trying to develop API for my apps using Firebase cloud functions.
Following this site to use the firebase emulator suite for development and testing locally.
Issue: The changes are not reflected in the locally emulated functions.
Steps:
index.js:
exports.test = functions.https.onRequest(async (request, response) => {
response.status(200).send("First");
});
Successfully deployed the test method.
firebase deploy --only functions:test
In Postman made the following GET request.
https://us-central1-<project-name>.cloudfunctions.net/test
Result: First
Status: 200 OK
Started the emulators:
firebase emulators:start --only functions
In Postman made the following GET request.
http://localhost:5001/<project-name>/us-central1/indexTest
Result: First
Status: 200 OK
Same as the actual deployed function.
Changed the function code to:
exports.test = functions.https.onRequest(async (request, response) => {
response.status(200).send("Second");
});
Getting the same result as before when hitting the emulated function in localhost. The changes are not reflected.
Also, tried stopping the emulator and starting it again. No luck.

I had raised an issue in the firebase-tools repo as suggested by DougStevenson.
Got the issue resolved with Sam Stern's support.
Posting the solution here for anyone else who gets stuck in the same issue.
Solution:
After every change to the ts files, run "npm run build" to compile the code again.
Change "build": "tsc" to "build": "tsc -w" in package.json if you want to auto-compile after every change.

Related

why process.env.FIRESTORE_EMULATOR_HOST will be undefined in test environment? even though I have run the emulator

I am using
Node 14
firebase functions-test: 0.2.3
firebase-admin: 9.6.0
firebase-functions: 3.13.2
firebase tools: 9.10.0
mocha: 8.3.2,
ts-node: 9.1.1
if I run firebase emulators:start
then I will expect process.env will have properties like
"FUNCTIONS_EMULATOR": "true",
"FIRESTORE_EMULATOR_HOST": "localhost:8080"
now I need to create testing for my cloud function using mocha, I will test it using Firestore and functions emulator, I give my file name events_cron_job.test.ts
after I run the simulator, I try to console log those values and I got undefined like this
I expect
console.log(process.env.FUNCTIONS_EMULATOR) // will be "true"
console.log(process.env.FIRESTORE_EMULATOR_HOST) // will be "localhost:8080"
I think this is only happen in test environment ( I am using mocha ), I have regular http triggers and it works as expected
before running the emulator, I try to execute this on terminal
export FIRESTORE_EMULATOR_HOST="localhost:8080"
export FUNCTIONS_EMULATOR="true"
and then execute firebase emulators:start , but the result will be the same, I still get undefined for those values
because those values are undefined, then if my laptop is disconnected from the internet, then this testing will not run ( i.e it will only access the data in production server, not the emulator! ), I expect the test will still run even if there is no internet connection since I use emulator.
I run the mocha test using
mocha -r ts-node/register src/tests/cloud_function_tests --recursive --extension .test.ts --timeout 60000 --exit
As you can see in this Github issue comment, in order to run tests with the Firebase emulator you should use the following command to trigger it:
firebase emulators:exec "npm test"
As this is how tests where intended to be executed by the Firebase Team using the emulator.

Firebase error: Function failed on loading user code (node.js)

I am a relative noob with Firebase my first time using it and following along with a tutorial. The tutorial is a bit outdated and I have been fixing bugs as I have been going, but this one has got me completely stuck. I try to run a different functions that trigger when a document is created
in certain collections. However, i get the following error:
Error
! functions[createNotificationOnlike(us-central1)]: Deployment error.
Function failed on loading user code. Error message: Error: please examine your function logs to see the error cause: https://cloud.google.com/functions/docs/monitoring/logging#viewing_logs
There are 3 more identical errors that correspond to the other exports in the following index.js file.
Index.js
exports.createNotificationOnlike = functions.firestore.document('likes/{id}').onCreate(async (snapshot) => {
try {
const doc = await db.doc(`posts/${snapshot.data().postId}`).get(); // we have access to user handle via the likes route
if(doc.exists){
await db.doc(`notifications/${snapshot.id}`).set({ // the id of the like is the same as the id of the notification that pertains to the like
createdAt: new Date().toISOString,
recipient: doc.data.userHandle,
sender: snapshot.data().userHandle,
type: 'like',
read: false,
postId: doc.id
});
return;
}
} catch (err) {
console.error(err);
return;
}
});
exports.removeNotificationOnUnlikePost = functions.firestore.document('likes/{id}').onDelete( async (snapshot) => {
try {
await db.doc(`notifications/${snapshot.id}`).delete();
return;
} catch (err) {
console.error(err);
return;
}
})
exports.createNotificationForComments = functions.firestore.document('comments/{id}').onCreate(async (snapshot) => {
try {
const doc = await db.doc(`posts/${snapshot.data().postId}`).get();
if(doc.exists){
db.doc(`notifications/${snapshot.id}`).set({
createdAt: new Date().toISOString,
recipient: doc.data.userHandle,
sender: snapshot.data().userHandle,
type: 'comment',
read: false,
postId: doc.id
})
return;
}
} catch (err) {
console.error(err);
return; // we dont need return messages since this isnt an endpoint it is a db trigger event
}
})
// auto turn the app into base route url/api
exports.api = functions.https.onRequest(app);
I have checked the logs as suggested by the error and i get the following messages which i think are useless, there are three other identical errors for the other functions
Error Logs
removeNotificationOnUnlikePost
{"#type":"type.googleapis.com/google.cloud.audit.AuditLog","status":{"code":3,"message":"Function failed on loading user code. Error message: Error: please examine your function logs to see the error cause: https://cloud.google.com/functions/docs/monitoring/logging#viewing_logs"}
Here is my package.json file
Package.json
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "10"
},
"dependencies": {
"busboy": "^0.3.1",
"express": "^4.17.1",
"firebase": "^7.16.0",
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.6.1"
},
"devDependencies": {
"firebase-functions-test": "^0.2.0"
},
"private": true
}
Lastly, here is my config stuff that was used to initialize everything:
admin.js
const admin = require('firebase-admin');
var serviceAccount = require('../../service-acct/socialapp-e5130-firebase-adminsdk-uo6p6-5495e18b97.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "socialapp-e5130.appspot.com",
databaseURL: "https://socialapp-e5130.firebaseio.com"
});
const db = admin.firestore();
module.exports = { db, admin }
Firebase init
const firebase = require('firebase');
const config = require('../util/config.js');
firebase.initializeApp(config);
P.S. It is worth mentioning that the http.onRequest trigger (api) was actually working and I have been developing without deploying using firebase serve. Now that I am ready to deploy these triggers something is going heinously wrong. Any help is greatly appreciated
Enter this command for getting log:
firebase functions:log
I had the exact same problem, trying to deploy the exact same code as you and I was just able to get it to deploy properly.
If yours is the same issue as mine, this is where the problem is:
var serviceAccount = require('../../service-acct/socialapp-e5130-firebase-adminsdk-uo6p6-5495e18b97.json');
It looks like when deploying to firebase you cant have code trying to reach outside your project folder, so the solution would be to have the key inside which isn't recommended or to set the environment variable GOOGLE_APPLICATION_CREDENTIALS to the file path of your service account key json file as explained in https://firebase.google.com/docs/admin/setup#windows and then just having
admin.initializeApp();
I had the same issue while setting up SendGrid.
I got this error because I accidentally installed SendGrid to the root folder instead of the functions folder.
Make sure to install your service to your functions folder using the cmd prompt. It should look something like this:
D:\firebaseProject\functions> npm i #sendgrid/mail
I had this error once and I fixed it by checking my code again, check maybe you require or import a dependency or module that you have not installed and try to check your package.json file make sure you installed all the necessary module and dependencies.
But mostly the problem is due to importing a module you did not install or there is a bug in your code
in my case i had a file starting with capital case letter, but require(...) with a lower case letter, it worked on Mac because it ignores it, as i understand, but deploying these functions results in this error.
Renaming file to lower case fixed this error for me.
Mine was because I tried to import a module that dependents that is not standard-alone
const axioms = require("axioms");
I solved it by adding the module name version to dependencies list in the package.json file.
"dependencies": {
....
"axios": "^0.27.2"
}
I got the module version with this command
npm view axios version
With the suggestion of the recommended answer I moved all my cloud functions code to the single index.js file (rather than 'require' statements) and it seemed to work for me. Note that I omitted any API keys and such and it still worked. I am assuming that since this deployment is handled by the firebase CLI it already knows this is a trusted environment.
I had this same problem. check if your package.json file has all the dependencies that you used to write firebase functions.
e.g. I had written a firebase function in which I called nodemailer dependancy but did not installed in package.json file of 'functions' directory
Note: Remember to install required packages in 'functions' directory only
Another way to view deployment-time logs is to go to the Logs Explorer in the Google Cloud Console.
If you're not familiar with Google Cloud console, maybe because you're only familiar with the Firebase console that's a simplified version of the Google Cloud console, then check out these docs for how to access the Logs Explorer on Google Cloud Console.
In my case, I had to access the logs from the Logs Explorer because when I ran firebase functions:log as suggested by Chinmay Atrawalkar, I got the error Error: Failed to list log entries Failed to retrieve log entries from Google Cloud..
Through the Logs Explorer, I could see the following error: Detailed stack trace: Error: Cannot find module 'file-saver'. From there I was able to figure out I just needed to add file-saver to my functions/package.json dependencies by running yarn add file-saver from my functions directory.

Problem running JS file with firebase emulators: exec

I was trying out the new Firebase Emulators UI announced on May 21, 2020. In the Firebase Docs, I noticed that there is a CLI command that allows us to run a script file:
firebase emulators:exec scriptpath
Run the script at scriptpath after starting emulators for the
Firebase products configured in firebase.json. Emulator processes will
automatically stop when the script has finished running.
Although the docs does not mention what kind of script file it should be, I presume it could be either a test script or a workable JavaScript file that be executed standalone with node filename.js without any errors. This makes sense because I can actually run an initial JS file that pre-populate some test data to the Firestore Emulator for further testing.
But since I've previously started the emulators, I have to open a new instance of command window to run the firebase emulators:exec command. Unfortunately, I hit the following errors:
D:\Firebase\my-project>firebase emulators:exec setup-db.js
i emulators: Starting emulators: functions, firestore, database, hosting, pubsub
! hub: emulator hub unable to start on port 4400, starting on 4401 instead.
! emulators: It seems that you are running multiple instances of the emulator suite for project my-project-id. This may result in unexpected behavior.
i emulators: Shutting down emulators.
i hub: Stopping emulator hub
! functions: Port 5001 is not open on localhost, could not start Functions Emulator.
! functions: To select a different host/port, specify that host/port in a firebase.json config file:
{
// ...
"emulators": {
"functions": {
"host": "HOST",
"port": "PORT"
}
}
}
i emulators: Shutting down emulators.
Error: Could not start Functions Emulator, port taken.
Take note of this line:
! emulators: It seems that you are running multiple instances of the emulator suite for project my-project-id. This may result in unexpected behavior.
So, how can I run the firebase emulators:exec AFTER the emulators start as mentioned in the Firebase Docs and, can I run the JS file for the said purpose? Thanks in advance!
UPDATE:
If I run just the firebase emulators:exec setup-imtp-db.js without prior running the firebase emulators:start command, I ran into the following errors:
Note that the setup-imtp-db.js does not contain any Cloud Functions code but just a standalone JS file that populates data from another JSON file into Firestore via Admin SDK. I'm using Node.js 12.14.0 and firebase-tools 8.4.0.
According to Firebase CLI Github README.md:
emulators:exec Start the local Firebase emulators, run a test
script, then shut down the emulators.
So the scriptpath parameter isn't really meant to run a JavaScript file that pre-populates data on Firestore Emulator (and presents on its Emulator UI) for the purpose of UI Functional Test or Integrated Test. The emulators:exec will immediately shut down the emulators after finish executing the test script.
As #Doug Stevenson suggested in the comment, if a proper error handling is expected from the emulators or a new feature is required, a request or bug report can be posted on Github for firebase-tools.
While the emulators:exec command is clearly not meant for seeding a database while continuing (manual) testing, as explained in the accepted answer, I did accomplish something of the kind.
I successfully prevented my emulators:exec-invoked database seeding script from exiting with the following method:
// db-seeder.js
const admin = require('firebase-admin');
const app = admin.initializeApp({
projectId: 'demo-test'
});
const db = admin.firestore(app);
...
// Prevents the seed Node.js process from exiting,
// which in turn prevents the emulators from exiting.
process.stdin.resume();
Along with the --ui switch, this gives an experience comparable to running emulators:start, but then with a seeded database:
firebase --project demo-test emulators:exec --ui db-seeder.js
I had to use this method because I was getting credential problems when trying to seed the database with a standalone script, see details here on dev.to.

How to run an Express API via Firebase Cloud Functions locally?

I have a standard REST API using Express, exposed via a Firebase Cloud Function.
const api = express()
api.get('/test', (req, res) => res.status(403).json({ "REASON": "UNAUTHORIZED" }))
exports.api = functions.https.onRequest(api)
Remote
When I deploy it and send GET https://<remote>/api/test via Postman I get 403 { "REASON": "UNAUTHORIZED" } as expected.
Local
When I run firebase emulators:start --only functions to serve these functions locally and test them, I do see functions: HTTP trigger initialized at http://localhost:5001/.../api in my terminal, but when I send GET http://localhost:5001/.../api/test via Postman I get 200 Not Found.
Am I missing something?
It was indeed a defect in the Firebase CLI, it seems to be fixed in the latest version (7.13.1)
Installed the latest version using: npm install -g firebase-tools#latest
Note: it did require me to update to compatible version of firebase-functions and firebase-admin as well.

How to collect code coverage with Istanbul when executing HTTP endpoints via Postman or Karate

I have a JS project that provides a set of endpoints leveraging Express with a typical express/router pattern.
const express = require('express');
const router = new express.Router();
router.post('/', async (req, res, next) => { });
router.get('/:abc', async (req, res, next) => { });
module.exports = router;
I can successfully start the server with npm start which calls node ./src/index.js and makes the endpoints available at https://localhost:8080
I can also successfully test these endpoints utilizing a tool like Postman or automation like Karate.
The problem i'm having is that I can't seem to collect code coverage using Istanbul when exercising the product source JS through http://localhost:8080.
I've tried npm start followed by nyc --all src/**/*.js gradlew test. The latter being automation that tests the endpoints. This results in 0% coverage which i'm assuming was due to not running nyc with npm start.
Next I tried nyc --all src/**/*.js npm start and noticed some coverage, but this was just coverage from starting the Express server.
Next I tried nyc --all src/**/*.js npm start followed by gradlew test and noticed the code coverage results were the same as when no endpoint tests were run.
Next I tried putting the prior two commands into a single JS script(myscript.js) running each asynchronously wherein the Express server was started before the gradle tests started running and ran nyc --all src/**/*.js myscript.js. The results from this were the same as my previous trial wherein only npm start received code coverage.
Next I tried nyc --all src/**/*.js npm start followed by nyc --all src/**/*.js -no-clean gradlew test and noticed the code coverage results were the same as when no endpoint tests were run.
Next I tried all of the attempts above by wrapping them into package.json scripts and running npm run <scriptName> getting the same exact behavior.
Finally I tried nyc instrument src instrumented/src --compact=false followed by npm run start:coverage wherein this start:coverage script calls the instrumented index.js at node ./instrumented/src/index.js followed by gradlew test followed by nyc report --reporter=lcov. This attempt also failing to produce any additional code coverage from the gradlew endpoint tests.
Doing some research online I came across this post
How do I setup code coverage on my Express based API?
And thought this looks eerily similar to my problems. Eg Istanbul doesn't know how to cover code when exercising the code through executing endpoints.
I decided to still post this as the above post is quite a bit old and wanted to get opinions and see if there is a better solution than
https://github.com/gotwarlost/istanbul-middleware
EDIT
Adding more specifics about how we start the Express server and run automation without Istanbul today. Just to clarify what we're working with and automation tools we're invested in. (Mainly Karate and Java)
/*
calls --> node -r dotenv/config src/index.js
*/
npm start
/*
calls --> gradlew clean test
this effectively calls a tool called Karate
Karate's base url is pointed to: https://locahost:8080
Karate tests execute endpoints on that base url
This would be akin to using Postman however Karate has quite a bit of configuration options
https://github.com/intuit/karate
*/
npm test
Through many hours of investigation we've managed to solve this. Prior project posted by #balexandre has been updated to illustrate how to do this.
https://github.com/kirksl/karate-istanbul
As said on the comments, you never start your server to run the tests... the tests will point to your server when you require the server file.
in my example, I'm running mocha with chai and the chai-http package helps to call the server
server.js
const app = require("express")();
// everything else ...
exports.server = app;
in your end-to-end tests, you can easily have:
const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
const server = require("./server.js").server;
...
it("should calculate the circumference", done => {
chai
.request(server) // <-- attach your server here
.get('/v1/circumference/10')
.end((err, res) => {
expect(res.status).to.be.eql(200);
expect(res.type).to.be.eql('application/json');
expect(res.body.result).to.eql(62.83185307179586);
done();
});
});
});
I've made a very simple project and pushed to GitHub so you can checkout and run everything, in order to see how all work together
GitHub Project
Added
I've added a route so it can show the coverage report (I used the html report) and created a static route to it ...
when you run the coverage npm run coverage it will generate the report inside ./report folder and a simple express route pointed to that folder, will enable one to see it as an endpoint.
commit info for such change

Resources