firebase / emulators-codelab

Apache License 2.0
46 stars 38 forks source link

Non Default Ports Do Not work #11

Open b0ot opened 3 years ago

b0ot commented 3 years ago
@samtstern @yuchenshi @rachelmyers I know this is a bug report and I shouldn't let my frustrations bleed through, but after spending weeks trying to get a WSL fix only to be forced to downgrade to WSL 1, I was pretty frustrated that I encountered another error so quickly. It has been weeks and countless hours trying to make it through a simple codelab.

Platform: Windows Build 19042.630 w/ WSL Version 1 Ubuntu 20.04 References: Initially I came across this issue while trying to follow Todd/Rachel's Youtube Video on Unit Security Testing Issue 61

Description: I did a clean setup for the emulators code lab and followed the instructions to step 8. The only change that I had to make is that my default ports are not free, so I had to modify the firebase.json file before running the emulators

{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": {
    "source": "functions"
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  },
  "emulators": {
    "functions": {
      "port": 5002
    },
    "firestore": {
      "port": 8085
    },
    "hosting": {
      "port": 5007
    }
  }
}

During step 8 when I go to run the mocha tests I get the following error:

Error: Timeout of 5000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

Based on my experimenting on my referenced issue, I believe that the mocha test doesn't know how to connect to the emulator on non-default ports. I had a work around for initializeTestApp, but could not find a workaround for initializeAdminApp.

samtstern commented 3 years ago

@b00t you've run into a lot of bugs and issues trying to use Firebase, so your frustration is 100% warranted. Thank you for continuing to file feedback.

The firebase.json file is only used by the Firebase CLI. Any other libraries which interact with the emulators, such as @firebase/rules-unit-testing or firebase-admin find the emulators through the FIREBASE_<FOO>_EMULATOR_HOST environment variables not through the firebase.json file.

You can either set these yourself, or you can run your tests through firebase emulators:exec 'npm run test' which will automatically set the right environment variables.

I will see if I can improve the error messages in this case so that future users don't get stuck.

rachelmyers commented 3 years ago

Just to echo @samtstern, this is great feedback; thanks.

samtstern commented 3 years ago

Looks like we already have warnings in the library: https://github.com/firebase/firebase-js-sdk/blob/master/packages/rules-unit-testing/src/api/index.ts#L216

Did you see anything in your logs like:

Warning: FIRESTORE_EMULATOR_HOST not set, using default value localhost:8080

If not, maybe mocha is hiding those logs?

b0ot commented 3 years ago

@rachelmyers Thanks.

@samtstern You are correct in that error appearing. The issue is I couldn't find any documentation on how to set the value. I also asked on other unofficial support channels (e.g. Slack) and searched r/firebase as well as stackoverflow. Given I'm very new to web programming so likely didn't understand the documentation properly.

When trying to follow Rachel and Todd's video on unit security rules ( Issue 61 ) eventually I was able to figure out a way to modify the function Todd wrote to change the port being used

const EMULATOR_PORT = 8086

function getFirestore(auth) {
  const db = firebase.initializeTestApp({projectId: MY_PROJECT_ID, auth: myAuth }).firestore();
  db.useEmulator("localhost", EMULATOR_PORT)
  return db
}

This seemed to work for this use case although I still got the error with Warning: FIRESTORE_EMULATOR_HOST not set, using default value localhost:8080

The issue was that useEmulator does not work for the Admin SDK

function getAdminFirestore() {
  const db = firebase.initializeAdminApp({projectId: MY_PROJECT_ID }).firestore();
  db.useEmulator("localhost", EMULATOR_PORT)
  return db
}

As you would get the error: ERROR: TypeError: db.useEmulator is not a function

Note, I probably tried things that seem ridiculous to use based on the documentation I tried Attempt 1: const FIRESTORE_EMULATOR_HOST = "localhost:8085"

Attempt 2: export FIRESTORE_EMULATOR_HOST = "localhost:8085"

Attempt 3: const db = firebase.initializeAdminApp({projectId: MY_PROJECT_ID, FIRESTORE_EMULATOR_HOST: "localhost:8085").firestore();

However none of those methods seemed to work.

Based on the recent post from sam

The firebase.json file is only used by the Firebase CLI. Any other libraries which interact with the emulators, such as @firebase/rules-unit-testing or firebase-admin find the emulators through the FIREBASE__EMULATOR_HOST environment variables not through the firebase.json file.

You can either set these yourself, or you can run your tests through firebase emulators:exec 'npm run test' which will automatically set the right environment variables.

It looks like you may actually set the env variables outside of the code in the shell, however I have no idea how to set them or manage them.

One last final aside: In the youtube video you may want to add a note about updating to const firebase = require('@firebase/rules-unit-testing'); I had issues when I originally followed the const firebase = require('@firebase/testing')

samtstern commented 3 years ago

@b0ot thanks for walking us through what you tried. It's really clear that we're making too many assumptions about what you and other web developers understand! We need to be much clearer.

Let me explain a bit:

FIRESTORE_EMULATOR_HOST is an environment variable. They are set in your terminal shell and can be accessed by programs you run from the command line, so they're popular in server environments. Using all caps and underscores is a convention for environment variables. Another common one you may run into is GOOGLE_APPLICATION_CREDENTIALS which all Google Cloud SDKs will use to locate credentials. Environment variables (env vars) are used to allow configuring server applications from the outside without requiring code changes.

Here's a super quick example of setting and using one:

$ MESSAGE="Hello world"
$ echo "$MESSAGE"
Hello world

So if you want to set one for your unit tests, you can just do it when you run the command:

FIRESTORE_EMULATOR_HOST=localhost:8088 npm run test

When you run firebase emulators:exec we set the environment for you before running your command.

yuchenshi commented 3 years ago

Drive-by comment in case anyone want this: If you have to set it within your code in Node.js (which we don't recommend), process.env.FIRESTORE_EMULATOR_HOST = "localhost:8085"; is the way to go. Make sure that is above any calls to the SDKs.

b0ot commented 3 years ago

@samtstern thanks for the quick reply and @yuchenshi for the additional information.

I was able to get it to run successfully by exporting the env variable in my shell (bash)!

I wasn't able to get it to work with firebase emulators:exec however from what I can determine it is because my emulators were setup in a parent folder above my test folder where mocha was installed.

I believe there is probably a way I could modify the package.json script files to make it work, but haven't figured it out currently. Main folder doesn't know about mocha, test folder wasn't starting the emulators correctly. I may experiment a bit more, but for now I'm happy that I have a way to make it work.

Eventually I'll make it through this codelab ... after all it says I only have 33 minute remaining :)

samtstern commented 3 years ago

@b0ot we seriously appreciate your patience and this thread has spawned a whole bunch of internal conversation about how we can make this easier and clearer. So your bad experience will at least help the next person!

samtstern commented 3 years ago

We're fixing this issue with two new methods here: https://github.com/firebase/firebase-js-sdk/pull/4388

Once that is merged and released, we'll update the docs to recommend them.

cdedreuille commented 3 years ago

I was also very confused about how to set up the environment variable correctly. I just want to share my use case in case it helps anyone. I'm running an app on Nextjs and I'm fetching data on the server using the admin sdk. On Nextjs you set up local environments in a .env.local file. I tried to paste export FIRESTORE_EMULATOR_HOST="localhost:8080" on my terminal but what I understand is that it creates a temporary environment variable.

As mentioned here, for permanent setting, you need to understand where to put the “export” script. Where here means Bash Shell Startup Script like /etc/profile, ~/.bash_profile, ~/.bashrc. But frankly I never really understood that part.

So in my case, what I ended up doing is:

if (process.env.NEXT_PUBLIC_DB_HOST === 'localhost') {
  process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
}
Justinohallo commented 3 years ago

In my package.json I used the following and it works:

"test": " FIRESTORE_EMULATOR_HOST=localhost:8088 FIREBASE_DATABASE_EMULATOR_HOST=localhost:9000 jest --watchAll --detectOpenHandles"