Skip to content

Commit 3939af1

Browse files
authored
Merge 010ae6f into d095ad3
2 parents d095ad3 + 010ae6f commit 3939af1

File tree

7 files changed

+126
-19
lines changed

7 files changed

+126
-19
lines changed

.changeset/moody-suits-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/rules-unit-testing': minor
3+
---
4+
5+
Add support for Storage emulator to rules-unit-testing

packages/rules-unit-testing/firebase.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"functions": {
1313
"port": 9004
1414
},
15+
"storage": {
16+
"port": 9199
17+
},
1518
"ui": {
1619
"enabled": false
1720
}

packages/rules-unit-testing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export {
3333
initializeTestApp,
3434
loadDatabaseRules,
3535
loadFirestoreRules,
36+
loadStorageRules,
3637
useEmulators,
3738
withFunctionTriggersDisabled
3839
} from './src/api';

packages/rules-unit-testing/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"build:deps": "lerna run --scope @firebase/rules-unit-testing --include-dependencies build",
1616
"dev": "rollup -c -w",
1717
"test:nyc": "TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --config ../../config/mocharc.node.js",
18-
"test": "firebase --project=foo --debug emulators:exec 'yarn test:nyc'",
18+
"test": "FIREBASE_CLI_PREVIEWS=storageemulator firebase --project=foo --debug emulators:exec 'yarn test:nyc'",
1919
"test:ci": "node ../../scripts/run_tests_in_ci.js -s test"
2020
},
2121
"license": "Apache-2.0",
@@ -29,15 +29,15 @@
2929
"devDependencies": {
3030
"@google-cloud/firestore": "4.8.1",
3131
"@types/request": "2.48.5",
32-
"firebase-admin": "9.4.2",
32+
"firebase-admin": "9.7.0",
3333
"firebase-tools": "9.1.0",
3434
"firebase-functions": "3.13.0",
3535
"rollup": "2.35.1",
3636
"rollup-plugin-typescript2": "0.29.0"
3737
},
3838
"peerDependencies": {
3939
"@google-cloud/firestore": "^4.2.0",
40-
"firebase-admin": "^9.0.0"
40+
"firebase-admin": "^9.7.0"
4141
},
4242
"repository": {
4343
"directory": "packages/rules-unit-testing",

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

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ const DATABASE_ADDRESS_ENV: string = 'FIREBASE_DATABASE_EMULATOR_HOST';
3131
/** The default address for the local database emulator. */
3232
const DATABASE_ADDRESS_DEFAULT: string = 'localhost:9000';
3333

34-
/** If any of environment variable is set, use it for the Firestore emulator. */
34+
/** If this environment variable is set, use it for the Firestore emulator. */
3535
const FIRESTORE_ADDRESS_ENV: string = 'FIRESTORE_EMULATOR_HOST';
3636
/** The default address for the local Firestore emulator. */
3737
const FIRESTORE_ADDRESS_DEFAULT: string = 'localhost:8080';
3838

39+
/** If this environment variable is set, use it for the Storage emulator. */
40+
const FIREBASE_STORAGE_ADDRESS_ENV: string = 'FIREBASE_STORAGE_EMULATOR_HOST';
41+
const CLOUD_STORAGE_ADDRESS_ENV: string = 'STORAGE_EMULATOR_HOST';
42+
/** The default address for the local Firestore emulator. */
43+
const STORAGE_ADDRESS_DEFAULT: string = 'localhost:9199';
44+
3945
/** Environment variable to locate the Emulator Hub */
4046
const HUB_HOST_ENV: string = 'FIREBASE_EMULATOR_HUB';
4147
/** The default address for the Emulator Hub */
@@ -47,6 +53,9 @@ let _databaseHost: string | undefined = undefined;
4753
/** The actual address for the Firestore emulator */
4854
let _firestoreHost: string | undefined = undefined;
4955

56+
/** The actual address for the Storage emulator */
57+
let _storageHost: string | undefined = undefined;
58+
5059
/** The actual address for the Emulator Hub */
5160
let _hubHost: string | undefined = undefined;
5261

@@ -133,6 +142,10 @@ export type FirebaseEmulatorOptions = {
133142
host: string;
134143
port: number;
135144
};
145+
storage?: {
146+
host: string;
147+
port: number;
148+
};
136149
hub?: {
137150
host: string;
138151
port: number;
@@ -193,6 +206,7 @@ export function apps(): firebase.app.App[] {
193206
export type AppOptions = {
194207
databaseName?: string;
195208
projectId?: string;
209+
storageBucket?: string;
196210
auth?: TokenOptions;
197211
};
198212
/** Construct an App authenticated with options.auth. */
@@ -201,19 +215,20 @@ export function initializeTestApp(options: AppOptions): firebase.app.App {
201215
? createUnsecuredJwt(options.auth, options.projectId)
202216
: undefined;
203217

204-
return initializeApp(jwt, options.databaseName, options.projectId);
218+
return initializeApp(jwt, options.databaseName, options.projectId, options.storageBucket);
205219
}
206220

207221
export type AdminAppOptions = {
208222
databaseName?: string;
209223
projectId?: string;
224+
storageBucket?: string;
210225
};
211226
/** Construct an App authenticated as an admin user. */
212227
export function initializeAdminApp(options: AdminAppOptions): firebase.app.App {
213228
const admin = require('firebase-admin');
214229

215230
const app = admin.initializeApp(
216-
getAppOptions(options.databaseName, options.projectId),
231+
getAppOptions(options.databaseName, options.projectId, options.storageBucket),
217232
getRandomAppName()
218233
);
219234

@@ -248,6 +263,10 @@ export function useEmulators(options: FirebaseEmulatorOptions): void {
248263
_firestoreHost = getAddress(options.firestore.host, options.firestore.port);
249264
}
250265

266+
if (options.storage) {
267+
_storageHost = getAddress(options.storage.host, options.storage.port);
268+
}
269+
251270
if (options.hub) {
252271
_hubHost = getAddress(options.hub.host, options.hub.port);
253272
}
@@ -301,6 +320,13 @@ export async function discoverEmulators(
301320
};
302321
}
303322

323+
if (data.storage) {
324+
options.storage = {
325+
host: data.storage.host,
326+
port: data.storage.port
327+
};
328+
}
329+
304330
if (data.hub) {
305331
options.hub = {
306332
host: data.hub.host,
@@ -351,6 +377,25 @@ function getFirestoreHost() {
351377
return _firestoreHost;
352378
}
353379

380+
function getStorageHost() {
381+
if (!_storageHost) {
382+
const fromEnv = process.env[FIREBASE_STORAGE_ADDRESS_ENV] || process.env[CLOUD_STORAGE_ADDRESS_ENV];
383+
if (fromEnv) {
384+
// The STORAGE_EMULATOR_HOST env var is an older Cloud Standard which includes http:// while
385+
// the FIREBASE_STORAGE_EMULATOR_HOST is a newer variable supported beginning in the Admin
386+
// SDK v9.7.0 which does not have the protocol.
387+
_storageHost = fromEnv.replace("http://", "");
388+
} else {
389+
console.warn(
390+
`Warning: ${FIREBASE_STORAGE_ADDRESS_ENV} not set, using default value ${STORAGE_ADDRESS_DEFAULT}`
391+
);
392+
_storageHost = STORAGE_ADDRESS_DEFAULT;
393+
}
394+
}
395+
396+
return _storageHost;
397+
}
398+
354399
function getHubHost() {
355400
if (!_hubHost) {
356401
const fromEnv = process.env[HUB_HOST_ENV];
@@ -367,34 +412,53 @@ function getHubHost() {
367412
return _hubHost;
368413
}
369414

415+
function parseHost(host: string): { hostname: string, port: number } {
416+
const u = new URL(host);
417+
return {
418+
hostname: u.hostname,
419+
port: Number.parseInt(u.port, 10)
420+
}
421+
}
422+
370423
function getRandomAppName(): string {
371424
return 'app-' + new Date().getTime() + '-' + Math.random();
372425
}
373426

427+
function getDatabaseUrl(databaseName: string) {
428+
return `http://${getDatabaseHost()}?ns=${databaseName}`;
429+
}
430+
374431
function getAppOptions(
375432
databaseName?: string,
376-
projectId?: string
433+
projectId?: string,
434+
storageBucket?: string,
377435
): { [key: string]: string } {
378436
let appOptions: { [key: string]: string } = {};
379437

380438
if (databaseName) {
381439
appOptions[
382440
'databaseURL'
383-
] = `http://${getDatabaseHost()}?ns=${databaseName}`;
441+
] = getDatabaseUrl(databaseName);
384442
}
443+
385444
if (projectId) {
386445
appOptions['projectId'] = projectId;
387446
}
388447

448+
if (storageBucket) {
449+
appOptions['storgeBucket'] = storageBucket;
450+
}
451+
389452
return appOptions;
390453
}
391454

392455
function initializeApp(
393456
accessToken?: string,
394457
databaseName?: string,
395-
projectId?: string
458+
projectId?: string,
459+
storageBucket?: string,
396460
): firebase.app.App {
397-
const appOptions = getAppOptions(databaseName, projectId);
461+
const appOptions = getAppOptions(databaseName, projectId, storageBucket);
398462
const app = firebase.initializeApp(appOptions, getRandomAppName());
399463
if (accessToken) {
400464
const mockAuthComponent = new Component(
@@ -417,17 +481,22 @@ function initializeApp(
417481
);
418482
}
419483
if (databaseName) {
484+
const { hostname, port } = parseHost(getDatabaseHost());
485+
app.database().useEmulator(hostname, port);
486+
420487
// Toggle network connectivity to force a reauthentication attempt.
421488
// This mitigates a minor race condition where the client can send the
422489
// first database request before authenticating.
423490
app.database().goOffline();
424491
app.database().goOnline();
425492
}
426493
if (projectId) {
427-
app.firestore().settings({
428-
host: getFirestoreHost(),
429-
ssl: false
430-
});
494+
const { hostname, port } = parseHost(getFirestoreHost());
495+
app.firestore().useEmulator(hostname, port);
496+
}
497+
if (storageBucket) {
498+
const { hostname, port } = parseHost(getStorageHost());
499+
app.storage().useEmulator(hostname, port);
431500
}
432501
/**
433502
Mute warnings for the previously-created database and whatever other
@@ -498,6 +567,31 @@ export async function loadFirestoreRules(
498567
}
499568
}
500569

570+
export type LoadStorageRulesOptions = {
571+
rules: string;
572+
};
573+
export async function loadStorageRules(
574+
options: LoadStorageRulesOptions
575+
): Promise<void> {
576+
if (!options.rules) {
577+
throw new Error('must provide rules to loadStorageRules');
578+
}
579+
580+
const resp = await requestPromise(request.put, {
581+
method: 'PUT',
582+
uri: `http://${getStorageHost()}/internal/setRules`,
583+
body: JSON.stringify({
584+
rules: {
585+
files: [{ name: 'storage.rules', content: options.rules }]
586+
}
587+
})
588+
});
589+
590+
if (resp.statusCode !== 200) {
591+
throw new Error(JSON.parse(resp.body.error));
592+
}
593+
}
594+
501595
export type ClearFirestoreDataOptions = {
502596
projectId: string;
503597
};

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ describe('Testing Module Tests', function () {
165165
host: 'localhost',
166166
port: 9003
167167
},
168+
storage: {
169+
host: 'localhost',
170+
port: 9199
171+
},
168172
hub: {
169173
host: 'localhost',
170174
port: 4400

yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7463,10 +7463,10 @@ fined@^1.0.1:
74637463
object.pick "^1.2.0"
74647464
parse-filepath "^1.0.1"
74657465

7466-
firebase-admin@9.4.2:
7467-
version "9.4.2"
7468-
resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz#190d5d7ca5e3f251d99503feb6e05e7ab1623851"
7469-
integrity sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==
7466+
firebase-admin@9.7.0:
7467+
version "9.7.0"
7468+
resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.7.0.tgz#c5fde178a1f1e757373b38601eb62a8ccbcb9393"
7469+
integrity sha512-dvxy4lK2P2BikE9lB2hj4dUXGm9tuoGRVtobXzCpk7uhi0/FuTOU1yp4IW7vlMI20fxTdm6FmCXdwUlMeSljBA==
74707470
dependencies:
74717471
"@firebase/database" "^0.8.1"
74727472
"@firebase/database-types" "^0.6.1"
@@ -7487,7 +7487,7 @@ [email protected]:
74877487
"@firebase/app-exp" "0.0.900"
74887488
"@firebase/auth-compat" "0.0.900"
74897489
"@firebase/auth-exp" "0.0.900"
7490-
"@firebase/database" "0.9.11"
7490+
"@firebase/database" "0.9.12"
74917491
"@firebase/firestore" "2.2.5"
74927492
"@firebase/functions-compat" "0.0.900"
74937493
"@firebase/functions-exp" "0.0.900"

0 commit comments

Comments
 (0)