Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 22 additions & 74 deletions scripts/emulator-tests/functionsEmulator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import * as logform from "logform";
import { EmulatedTriggerDefinition } from "../../src/emulator/functionsEmulatorShared";
import { FunctionsEmulator } from "../../src/emulator/functionsEmulator";
import { Emulators } from "../../src/emulator/types";
import { FakeEmulator } from "../../src/test/emulators/fakeEmulator";
import { TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from "./fixtures";
import { logger } from "../../src/logger";
import * as registry from "../../src/emulator/registry";
import * as secretManager from "../../src/gcp/secretManager";
import { findAvailablePort } from "../../src/emulator/portUtils";

if ((process.env.DEBUG || "").toLowerCase().includes("spec")) {
const dropLogLevels = (info: logform.TransformableInfo) => info.message;
Expand Down Expand Up @@ -612,29 +614,31 @@ describe("FunctionsEmulator-Hub", function () {
});

describe("environment variables", () => {
let emulatorRegistryStub: sinon.SinonStub;

beforeEach(() => {
emulatorRegistryStub = sinon.stub(registry.EmulatorRegistry, "getInfo").returns(undefined);
});
const host = "127.0.0.1";
const startFakeEmulator = async (emulator: Emulators): Promise<number> => {
const port = await findAvailablePort(host, 4000);
const fake = new FakeEmulator(emulator, host, port);
await registry.EmulatorRegistry.start(fake);
return port;
};

afterEach(() => {
emulatorRegistryStub.restore();
return registry.EmulatorRegistry.stopAll();
});

it("should set FIREBASE_DATABASE_EMULATOR_HOST when the emulator is running", async () => {
emulatorRegistryStub.withArgs(Emulators.DATABASE).returns({
name: Emulators.DATABASE,
host: "localhost",
port: 9090,
});
it("should set env vars when the emulator is running", async () => {
const databasePort = await startFakeEmulator(Emulators.DATABASE);
const firestorePort = await startFakeEmulator(Emulators.FIRESTORE);
const authPort = await startFakeEmulator(Emulators.AUTH);

await useFunction(emu, "functionId", () => {
return {
functionId: require("firebase-functions").https.onRequest(
(_req: express.Request, res: express.Response) => {
res.json({
var: process.env.FIREBASE_DATABASE_EMULATOR_HOST,
databaseHost: process.env.FIREBASE_DATABASE_EMULATOR_HOST,
firestoreHost: process.env.FIRESTORE_EMULATOR_HOST,
authHost: process.env.FIREBASE_AUTH_EMULATOR_HOST,
});
}
),
Expand All @@ -645,70 +649,14 @@ describe("FunctionsEmulator-Hub", function () {
.get("/fake-project-id/us-central1/functionId")
.expect(200)
.then((res) => {
expect(res.body.var).to.eql("localhost:9090");
});
}).timeout(TIMEOUT_MED);

it("should set FIRESTORE_EMULATOR_HOST when the emulator is running", async () => {
emulatorRegistryStub.withArgs(Emulators.FIRESTORE).returns({
name: Emulators.FIRESTORE,
host: "localhost",
port: 9090,
});

await useFunction(emu, "functionId", () => {
return {
functionId: require("firebase-functions").https.onRequest(
(_req: express.Request, res: express.Response) => {
res.json({
var: process.env.FIRESTORE_EMULATOR_HOST,
});
}
),
};
});

await supertest(emu.createHubServer())
.get("/fake-project-id/us-central1/functionId")
.expect(200)
.then((res) => {
expect(res.body.var).to.eql("localhost:9090");
});
}).timeout(TIMEOUT_MED);

it("should set AUTH_EMULATOR_HOST when the emulator is running", async () => {
emulatorRegistryStub.withArgs(Emulators.AUTH).returns({
name: Emulators.AUTH,
host: "localhost",
port: 9099,
});

await useFunction(emu, "functionId", () => {
return {
functionId: require("firebase-functions").https.onRequest(
(_req: express.Request, res: express.Response) => {
res.json({
var: process.env.FIREBASE_AUTH_EMULATOR_HOST,
});
}
),
};
});

await supertest(emu.createHubServer())
.get("/fake-project-id/us-central1/functionId")
.expect(200)
.then((res) => {
expect(res.body.var).to.eql("localhost:9099");
expect(res.body.databaseHost).to.eql(`${host}:${databasePort}`);
expect(res.body.firestoreHost).to.eql(`${host}:${firestorePort}`);
expect(res.body.authHost).to.eql(`${host}:${authPort}`);
});
}).timeout(TIMEOUT_MED);

it("should return an emulated databaseURL when RTDB emulator is running", async () => {
emulatorRegistryStub.withArgs(Emulators.DATABASE).returns({
name: Emulators.DATABASE,
host: "localhost",
port: 9090,
});
const databasePort = await startFakeEmulator(Emulators.DATABASE);

await useFunction(emu, "functionId", () => {
return {
Expand All @@ -725,7 +673,7 @@ describe("FunctionsEmulator-Hub", function () {
.expect(200)
.then((res) => {
expect(res.body.databaseURL).to.eql(
"http://localhost:9090/?ns=fake-project-id-default-rtdb"
`http://${host}:${databasePort}/?ns=fake-project-id-default-rtdb`
);
});
}).timeout(TIMEOUT_MED);
Expand Down
40 changes: 22 additions & 18 deletions src/commands/emulators-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,21 @@ function printEmulatorOverview(options: any): void {
}
const reservedPortsString = reservedPorts.length > 0 ? reservedPorts.join(", ") : "None";

const uiInfo = EmulatorRegistry.getInfo(Emulators.UI);
const hubInfo = EmulatorRegistry.getInfo(Emulators.HUB);
const uiUrl = uiInfo ? `http://${EmulatorRegistry.getInfoHostString(uiInfo)}` : "unknown";
const uiRunning = EmulatorRegistry.isRunning(Emulators.UI);
const head = ["Emulator", "Host:Port"];

if (uiInfo) {
if (uiRunning) {
head.push(`View in ${Constants.description(Emulators.UI)}`);
}

const successMessageTable = new Table();
let successMsg = `${clc.green("✔")} ${clc.bold(
"All emulators ready! It is now safe to connect your app."
)}`;
if (uiInfo) {
successMsg += `\n${clc.cyan("i")} View Emulator UI at ${stylizeLink(uiUrl)}`;
if (uiRunning) {
successMsg += `\n${clc.cyan("i")} View Emulator UI at ${stylizeLink(
EmulatorRegistry.url(Emulators.UI).toString()
)}`;
}
successMessageTable.push([successMsg]);

Expand All @@ -95,22 +95,26 @@ function printEmulatorOverview(options: any): void {
.map((emulator) => {
const emulatorName = Constants.description(emulator).replace(/ emulator/i, "");
const isSupportedByUi = EMULATORS_SUPPORTED_BY_UI.includes(emulator);
// The Extensions emulator runs as part of the Functions emulator, so display the Functions emulators info instead.
const info = EmulatorRegistry.getInfo(emulator);
if (!info) {
return [emulatorName, "Failed to initialize (see above)", "", ""];
const listen = commandUtils.getListenOverview(emulator);
if (!listen) {
const row = [emulatorName, "Failed to initialize (see above)"];
if (uiRunning) {
row.push("");
}
}
let uiLink = "n/a";
if (isSupportedByUi && uiRunning) {
const url = EmulatorRegistry.url(Emulators.UI);
url.pathname = `/${emulator}`;
uiLink = stylizeLink(url.toString());
}

return [
emulatorName,
EmulatorRegistry.getInfoHostString(info),
isSupportedByUi && uiInfo ? stylizeLink(`${uiUrl}/${emulator}`) : clc.blackBright("n/a"),
];
return [emulatorName, listen, uiLink];
})
.map((col) => col.slice(0, head.length))
.filter((v) => v)
);
let extensionsTable: string = "";
let extensionsTable = "";
if (EmulatorRegistry.isRunning(Emulators.EXTENSIONS)) {
const extensionsEmulatorInstance = EmulatorRegistry.get(
Emulators.EXTENSIONS
Expand All @@ -121,8 +125,8 @@ function printEmulatorOverview(options: any): void {

${emulatorsTable}
${
hubInfo
? clc.blackBright(" Emulator Hub running at ") + EmulatorRegistry.getInfoHostString(hubInfo)
EmulatorRegistry.isRunning(Emulators.HUB)
? clc.blackBright(" Emulator Hub running at ") + EmulatorRegistry.url(Emulators.HUB).host
: clc.blackBright(" Emulator Hub not running.")
}
${clc.blackBright(" Other reserved ports:")} ${reservedPortsString}
Expand Down
24 changes: 7 additions & 17 deletions src/emulator/auth/cloudFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as uuid from "uuid";

import { EventContext } from "firebase-functions";
import { Client } from "../../apiv2";

import { EmulatorInfo, Emulators } from "../types";
import { Emulators } from "../types";
import { EmulatorLogger } from "../emulatorLogger";
import { EmulatorRegistry } from "../registry";
import { UserInfo } from "./state";
Expand All @@ -16,22 +15,10 @@ type CreateEvent = EventContext & {

export class AuthCloudFunction {
private logger = EmulatorLogger.forEmulator(Emulators.AUTH);
private functionsEmulatorInfo?: EmulatorInfo;
private multicastOrigin = "";
private multicastPath = "";
private enabled = false;

constructor(private projectId: string) {
const functionsEmulator = EmulatorRegistry.get(Emulators.FUNCTIONS);

if (functionsEmulator) {
this.enabled = true;
this.functionsEmulatorInfo = functionsEmulator.getInfo();
this.multicastOrigin = `http://${EmulatorRegistry.getInfoHostString(
this.functionsEmulatorInfo
)}`;
this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
}
this.enabled = EmulatorRegistry.isRunning(Emulators.FUNCTIONS);
}

public async dispatch(action: AuthCloudFunctionAction, user: UserInfo): Promise<void> {
Expand All @@ -40,11 +27,14 @@ export class AuthCloudFunction {
const userInfoPayload = this.createUserInfoPayload(user);
const multicastEventBody = this.createEventRequestBody(action, userInfoPayload);

const c = new Client({ urlPrefix: this.multicastOrigin, auth: false });
const c = EmulatorRegistry.client(Emulators.FUNCTIONS);
let res;
let err: Error | undefined;
try {
res = await c.post(this.multicastPath, multicastEventBody);
res = await c.post(
`/functions/projects/${this.projectId}/trigger_multicast`,
multicastEventBody
);
} catch (e: any) {
err = e;
}
Expand Down
4 changes: 2 additions & 2 deletions src/emulator/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class AuthEmulator implements EmulatorInstance {
await importFromFile(
{
method: "PATCH",
host,
host: utils.connectableHostname(host),
port,
path: `/emulator/v1/projects/${projectId}/config`,
headers: {
Expand All @@ -110,7 +110,7 @@ export class AuthEmulator implements EmulatorInstance {
await importFromFile(
{
method: "POST",
host,
host: utils.connectableHostname(host),
port,
path: `/identitytoolkit.googleapis.com/v1/projects/${projectId}/accounts:batchCreate`,
headers: {
Expand Down
2 changes: 1 addition & 1 deletion src/emulator/auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function logError(err: Error): void {
* terminal or Emulator UI).
*/
export function authEmulatorUrl(req: express.Request): URL {
if (EmulatorRegistry.getInfo(Emulators.AUTH)) {
if (EmulatorRegistry.isRunning(Emulators.AUTH)) {
return EmulatorRegistry.url(Emulators.AUTH);
} else {
return EmulatorRegistry.url(Emulators.AUTH, req);
Expand Down
Loading