Skip to content

Commit 27bd7b2

Browse files
committed
Implement port discovery in rules-unit-testing
1 parent 6766da2 commit 27bd7b2

File tree

2 files changed

+151
-8
lines changed

2 files changed

+151
-8
lines changed

packages/rules-unit-testing/src/api/index.ts

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import * as request from 'request';
2222
import { base64 } from '@firebase/util';
2323
import { setLogLevel, LogLevel } from '@firebase/logger';
2424
import { Component, ComponentType } from '@firebase/component';
25+
import { option } from 'yargs';
2526

2627
const { firestore, database } = firebase;
2728
export { firestore, database };
@@ -38,7 +39,7 @@ const FIRESTORE_ADDRESS_DEFAULT: string = 'localhost:8080';
3839

3940
/** Environment variable to locate the Emulator Hub */
4041
const HUB_HOST_ENV: string = 'FIREBASE_EMULATOR_HUB';
41-
/** The default address for the Emulator hub */
42+
/** The default address for the Emulator Hub */
4243
const HUB_HOST_DEFAULT: string = 'localhost:4400';
4344

4445
/** The actual address for the database emulator */
@@ -47,6 +48,9 @@ let _databaseHost: string | undefined = undefined;
4748
/** The actual address for the Firestore emulator */
4849
let _firestoreHost: string | undefined = undefined;
4950

51+
/** The actual address for the Emulator Hub */
52+
let _hubHost: string | undefined = undefined;
53+
5054
export type Provider =
5155
| 'custom'
5256
| 'email'
@@ -118,6 +122,24 @@ export type FirebaseIdToken = {
118122
// new users should prefer 'sub' instead.
119123
export type TokenOptions = Partial<FirebaseIdToken> & { uid?: string };
120124

125+
/**
126+
* Host/port configuration for applicable Firebase Emulators.
127+
*/
128+
export type FirebaseEmulatorOptions = {
129+
firestore?: {
130+
host: string;
131+
port: number;
132+
};
133+
database?: {
134+
host: string;
135+
port: number;
136+
};
137+
hub?: {
138+
host: string;
139+
port: number;
140+
};
141+
};
142+
121143
function createUnsecuredJwt(token: TokenOptions, projectId?: string): string {
122144
// Unsecured JWTs use "none" as the algorithm.
123145
const header = {
@@ -206,6 +228,98 @@ export function initializeAdminApp(options: AdminAppOptions): firebase.app.App {
206228
return app;
207229
}
208230

231+
/**
232+
* Set the host and port configuration for applicable emulators. This will override any values
233+
* found in environment variables. Must be called before initializeAdminApp or initializeTestApp.
234+
*
235+
* @param options options object.
236+
*/
237+
export function useEmulators(options: FirebaseEmulatorOptions): void {
238+
if (!(options.database || options.firestore || options.hub)) {
239+
throw new Error(
240+
"Argument to useEmulators must contain at least one of 'database', 'firestore', or 'hub'."
241+
);
242+
}
243+
244+
if (options.database) {
245+
_databaseHost = getAddress(options.database.host, options.database.port);
246+
}
247+
248+
if (options.firestore) {
249+
_firestoreHost = getAddress(options.firestore.host, options.firestore.port);
250+
}
251+
252+
if (options.hub) {
253+
_hubHost = getAddress(options.hub.host, options.hub.port);
254+
}
255+
}
256+
257+
/**
258+
* Use the Firebase Emulator hub to discover other running emulators. Call useEmulators() with
259+
* the result to configure the library to use the discovered emulators.
260+
*
261+
* @param hubHost the host where the Emulator Hub is running (ex: 'localhost')
262+
* @param hubPort the port where the Emulator Hub is running (ex: 4400)
263+
*/
264+
export async function discoverEmulators(
265+
hubHost?: string,
266+
hubPort?: number
267+
): Promise<FirebaseEmulatorOptions> {
268+
if ((hubHost && !hubPort) || (!hubHost && hubPort)) {
269+
throw new Error(
270+
`Invalid configuration hubHost=${hubHost} and hubPort=${hubPort}. If either parameter is supplied, both must be defined.`
271+
);
272+
}
273+
274+
const hubAddress =
275+
hubHost && hubPort ? getAddress(hubHost, hubPort) : getHubHost();
276+
277+
const res = await requestPromise(request.get, {
278+
method: 'GET',
279+
uri: `http://${hubAddress}/emulators`
280+
});
281+
if (res.statusCode !== 200) {
282+
throw new Error(
283+
`HTTP Error ${res.statusCode} when attempting to reach Emulator Hub at ${hubAddress}, are you sure it is running?`
284+
);
285+
}
286+
287+
const options: FirebaseEmulatorOptions = {};
288+
289+
const data = JSON.parse(res.body);
290+
291+
if (data.database) {
292+
options.database = {
293+
host: data.database.host,
294+
port: data.database.port
295+
};
296+
}
297+
298+
if (data.firestore) {
299+
options.firestore = {
300+
host: data.firestore.host,
301+
port: data.firestore.port
302+
};
303+
}
304+
305+
if (data.hub) {
306+
options.hub = {
307+
host: data.hub.host,
308+
port: data.hub.port
309+
};
310+
}
311+
312+
return options;
313+
}
314+
315+
function getAddress(host: string, port: number) {
316+
if (host.includes('::')) {
317+
return `[${host}]:${port}`;
318+
} else {
319+
return `${host}:${port}`;
320+
}
321+
}
322+
209323
function getDatabaseHost() {
210324
if (!_databaseHost) {
211325
const fromEnv = process.env[DATABASE_ADDRESS_ENV];
@@ -238,6 +352,22 @@ function getFirestoreHost() {
238352
return _firestoreHost;
239353
}
240354

355+
function getHubHost() {
356+
if (!_hubHost) {
357+
const fromEnv = process.env[HUB_HOST_ENV];
358+
if (fromEnv) {
359+
_hubHost = fromEnv;
360+
} else {
361+
console.warn(
362+
`Warning: ${HUB_HOST_ENV} not set, using default value ${HUB_HOST_DEFAULT}`
363+
);
364+
_hubHost = HUB_HOST_DEFAULT;
365+
}
366+
}
367+
368+
return _hubHost;
369+
}
370+
241371
function getRandomAppName(): string {
242372
return 'app-' + new Date().getTime() + '-' + Math.random();
243373
}
@@ -406,13 +536,7 @@ export async function clearFirestoreData(
406536
export async function withFunctionTriggersDisabled<TResult>(
407537
fn: () => TResult | Promise<TResult>
408538
): Promise<TResult> {
409-
let hubHost = process.env[HUB_HOST_ENV];
410-
if (!hubHost) {
411-
console.warn(
412-
`${HUB_HOST_ENV} is not set, assuming the Emulator hub is running at ${HUB_HOST_DEFAULT}`
413-
);
414-
hubHost = HUB_HOST_DEFAULT;
415-
}
539+
const hubHost = getHubHost();
416540

417541
// Disable background triggers
418542
const disableRes = await requestPromise(request.put, {

packages/rules-unit-testing/test/database.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ describe('Testing Module Tests', function () {
128128
.catch(() => {});
129129
});
130130

131+
it('discoverEmulators() finds all running emulators', async () => {
132+
const options = await firebase.discoverEmulators();
133+
134+
expect(options).to.deep.equal({
135+
database: {
136+
host: 'localhost',
137+
port: 9002
138+
},
139+
firestore: {
140+
host: 'localhost',
141+
port: 9003
142+
},
143+
hub: {
144+
host: 'localhost',
145+
port: 4400
146+
}
147+
});
148+
});
149+
131150
it('initializeTestApp() with auth=null does not set access token', async function () {
132151
const app = firebase.initializeTestApp({
133152
projectId: 'foo',

0 commit comments

Comments
 (0)