Skip to content

Pub/Sub and Eventarc

Fireemu serves the google.pubsub.v1 API over gRPC and REST at 127.0.0.1:8085. Pub/Sub starts when it is configured: emulators.pubsub in firebase.json, daemon.pubsubPort, --pubsub-port, or --only pubsub.

Topics, subscriptions, publish, pull and acknowledgement, push delivery, filters, ordering keys, dead-letter topics, StreamingPull, snapshots and seek are supported. Schemas, BigQuery and Cloud Storage subscription delivery, and some APIs such as UpdateTopic are not.

fireemu exec sets PUBSUB_EMULATOR_HOST, which the Google Cloud client libraries read (see Environment variables).

A topic’s messageStoragePolicy (allowedPersistenceRegions and enforceInTransit) is stored, returned by get and list, and changed by UpdateTopic (REST PATCH with updateMask, masks messageStoragePolicy and its two members) under profile = "emulator", exactly as sent, as the official emulator does: an unknown, repeated or empty region list, and an empty policy, are all kept. A mask that names the policy but sends none removes it. Nothing is enforced: messages are not routed by region. The strict profile refuses the field with UNIMPLEMENTED (REST 501) and an explanation, because production checks every region against Google Cloud’s Pub/Sub region list (it answered INVALID_ARGUMENT for no-such-region1 and accepted us-central1) and Fireemu has no cited copy of that list to check against. Leave the field out under strict, or select the emulator profile to keep it.

Under profile = "emulator", topic kmsKeyName and tags are stored as sent and returned over REST and gRPC without validation or enforcement. UpdateTopic can replace or clear kms_key_name; tags are immutable. The strict profile refuses these fields because production validation depends on Cloud KMS and Resource Manager inventories and permissions that Fireemu does not have. The official emulator 0.8.35 stores these values unvalidated and refuses every update mask; production behavior is not recorded.

Under strict a message that exhausts maxDeliveryAttempts is forwarded to the dead-letter topic with no project number and no IAM grants, with the metadata attributes production adds. Production forwards only after the Pub/Sub service agent holds roles/pubsub.subscriber on the subscription and roles/pubsub.publisher on the dead-letter topic; set pubsub.deadLetterAuthorization to "iam" (or productionExact, or name pubsub.projectNumbers) to require the same grants through SetIamPolicy. The start banner then names the service agent to grant.

Under strict, a request without any credential is accepted, so clients that use PUBSUB_EMULATOR_HOST without credentials work. A request that presents a credential is checked, and Fireemu cannot verify a real Google OAuth token offline, so such a request is refused with 401. Use a credential-free client against the emulator, for example the Java client with NoCredentialsProvider.

For tests that need a credential, set a local fixture bearer token. The endpoint then accepts only Authorization: Bearer <token> with that exact value, and refuses requests without a credential as production does:

# Configuration schema version: 1.
schemaVersion = 1
[pubsub.authentication]
# Require this non-empty local bearer token for strict-profile Pub/Sub requests.
localBearerToken = "fixture-token"

Use a made-up value; never reuse a production credential.

When a Functions codebase is loaded, Fireemu starts an Eventarc listener at 127.0.0.1:9299 and sets CLOUD_EVENTARC_EMULATOR_HOST. Functions declared with onCustomEventPublished receive the events published to their channel.

The Admin SDK always asks its credential for an access token before publishing, and CI has no Google credential. Give the app a fake credential:

import { initializeApp } from 'firebase-admin/app';
import { getEventarc } from 'firebase-admin/eventarc';
const app = initializeApp({
projectId: 'demo-app',
credential: { getAccessToken: async () => ({ access_token: 'owner', expires_in: 3600 }) },
});
await getEventarc(app).channel('locations/us-central1/channels/firebase').publish({
type: 'com.example.thing.done',
source: 'https://example.com/things',
data: { id: 'e1' },
});

By default the listener does not check the credential (eventarc.authentication: "permissive"), as the official emulator does not. With "production" (or productionExact), a missing, malformed or JWT-shaped bearer is refused with production’s 401.

When a Functions codebase is loaded, Fireemu starts a Cloud Tasks emulator at 127.0.0.1:9499 and sets CLOUD_TASKS_EMULATOR_HOST. Task queue functions (onTaskDispatched) receive the tasks enqueued for them, with their rate limits and retry configuration. A task’s scheduleTime holds it back under strict; see Task queues for waiting tasks, Run now and the virtual clock.