Firebase storage unit tests: Timeout when trying to upload files using emulator

When running the below test via npm test, I get the following output:

Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/Users/[myname]/development/Construct/construct/infra/test/storage.test.js)
      at listOnTimeout (node:internal/timers:564:17)
      at process.processTimers (node:internal/timers:507:7)

I have the storage emulator running, with output telling me it is running on 127.0.0.1:9199

You'll notice I have a debug statement in storage.rules. This is consistently outputting a result, so the rules do appear to be getting run.

I can't find definitive documentation on what env var I should be setting, but it seems like process.env.STORAGE_EMULATOR_HOST is probably the right one?

My colleague, running the same test on his machine, is finding it runs successfully. So it seems the problem is somehow connected to my local setup.

I also have tests that test firestore.rules as well as storage.rules (running against the Firestore emulator) and they run with no problem.

My environment

node -v: v18.0.0 npm -v: 8.6.0 firebase --version: 13.32.0 macOS 15.3.1 (24D70)

Related links

I came across this while searching for a solution: https://github.com/firebase/firebase-tools/issues/4908 I appear to have the same problem.

Steps to reproduce

Run the storage emulator, check the host and port match what's being set in STORAGE_EMULATOR_HOST in storage.test.js, then run the test in storage.test.js via npm test.

Expected behavior

I expect to get feedback on whether my test has passed or failed, as I do for my firestore.rules tests, eg "PERMISSION_DENIED" when I expect permission to be denied, and a green tick when tests pass.

My code

storage.test.js:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
} from "@firebase/rules-unit-testing";
import { ref, uploadBytes } from "firebase/storage";
import { readFileSync } from "fs";
import { afterEach, describe, it } from "mocha";
import { v4 as uuid_v4 } from "uuid";

process.env.STORAGE_EMULATOR_HOST = "127.0.0.1:9199";
//process.env.DATASTORE_EMULATOR_HOST = "127.0.0.1:9199";
const projectId = "app-construct-firebase-storage-security-rules-test";
const firebaseStorage = {
  rules: readFileSync("./../storage.rules", "utf8"),
  host: "127.0.0.1",
  port: 9199,
};

const testEnv = await initializeTestEnvironment({
  projectId: projectId,
  storage: firebaseStorage,
});

describe("firebase storage security rules unit tests", () => {
  const userID = uuid_v4();
  const userEmail = "[email protected]";

  const tokenOptions = {
    email: userEmail,
  };

  function avatarPath(myUserID) {
    return `avatars/${myUserID}`;
  }

  describe("********** `avatars` storage **********", () => {
    it("users can only create avatars stored against their own user ID", async () => {
      const storage = testEnv
        .authenticatedContext(userID, tokenOptions)
        .storage();
      const image = readFileSync("./dummy-avatar.png");
      const storagePath = avatarPath(userID);

      const userImageRef = storage.ref(storagePath);
      await assertSucceeds(
        userImageRef.put(image, { contentType: "image/png" })
      );
    });
  });
});

storage.rules:

rules_version = '2';

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read;
    }

    match /avatars/{userID} {
      allow write, delete: if debug(request.auth.uid) == debug(userID);
    }
  }
}

firebase.json:

{
  "hosting": {
    "public": "app/build/web",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "firestore": {
    "rules": "infra/firestore.rules"
  },
  "storage": {
    "rules": "infra/storage.rules"
  },
  "emulators": {
    "auth": {
      "port": 9099
    },
    "firestore": {
      "port": 8080
    },
    "storage": {
      "port": 9199
    },
    "ui": {
      "enabled": true
    },
    "singleProjectMode": false
  },
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log"]
    }
  ]
}

package.json:

{
  "name": "security-rules-tests",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "mocha \"./storage.test.js\" --exit"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@firebase/rules-unit-testing": "^3.0.4",
    "firebase-admin": "^12.7.0",
    "firebase-tools": "^13.20.2",
    "mocha": "^10.7.3",
    "uuid": "^11.0.3"
  }
}

Debug info

npm test --debug gives the same output.

But if I check firestore-debug.log, I see this:

Mar 03, 2025 8:52:05 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead INFO: Detected non-HTTP/2 connection.

...and if I check firebase-debug.log, I see this:

[debug] [2025-03-03T20:55:36.909Z] >>> [apiv2][query] POST http://127.0.0.1:5001/functions/projects/app-construct-dv-428210/trigger_multicast [none] [debug] [2025-03-03T20:55:36.909Z] >>> [apiv2][body] POST http://127.0.0.1:5001/functions/projects/app-construct-dv-428210/trigger_multicast {"specversion":"1.0","id":"25e21402-0f42-4bf9-9197-7414f86c2eae","type":"google.cloud.storage.object.v1.finalized","source":"//storage.googleapis.com/projects/_/buckets/app-construct-firebase-storage-security-rules-test/objects/avatars/37d5ca50-211d-4d0c-993c-771c5668e0f8","time":"2025-03-03T20:55:36.888Z","data":{"kind":"storage#object","name":"avatars/37d5ca50-211d-4d0c-993c-771c5668e0f8","bucket":"app-construct-firebase-storage-security-rules-test","generation":"1741035336888","metageneration":"1","contentType":"image/png","timeCreated":"2025-03-03T20:55:36.888Z","updated":"2025-03-03T20:55:36.888Z","storageClass":"STANDARD","size":"561","md5Hash":"nWSYBL5eqqiZUZaMdncOqg==","etag":"3ZOhitoec/6YbVv42oz62nkv+BY","metadata":{"firebaseStorageDownloadTokens":"f5e7cfc7-8d49-43c5-b642-5345153f2748"},"crc32c":"16/Irg==","timeStorageClassUpdated":"2025-03-03T20:55:36.888Z","id":"app-construct-firebase-storage-security-rules-test/avatars/37d5ca50-211d-4d0c-993c-771c5668e0f8/1741035336888","selfLink":"http://127.0.0.1:9199/storage/v1/b/app-construct-firebase-storage-security-rules-test/o/avatars%2F37d5ca50-211d-4d0c-993c-771c5668e0f8","mediaLink":"http://127.0.0.1:9199/download/storage/v1/b/app-construct-firebase-storage-security-rules-test/o/avatars%2F37d5ca50-211d-4d0c-993c-771c5668e0f8?generation=1741035336888&alt=media"}} [debug] [2025-03-03T20:55:36.914Z] <<< [apiv2][status] POST http://127.0.0.1:5001/functions/projects/app-construct-dv-428210/trigger_multicast 200 [debug] [2025-03-03T20:55:36.914Z] <<< [apiv2][body] POST http://127.0.0.1:5001/functions/projects/app-construct-dv-428210/trigger_multicast {"status":"multicast_acknowledged"}

...and if I check database-debug.log, I see nothing recent.

(I'm not actually sure which of these files is the one relevant to storage rules?)

Upvotes: 0

Views: 13

Answers (0)

Related Questions