Skip to content

Migrate testing to rules-unit-testing #3378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b336c8b
Migrate testing to rules-unit-testing
samtstern Jul 9, 2020
ff83ad6
Remove bad dep
samtstern Jul 9, 2020
543fbb3
Merge branch 'master' into ss-fork-testing
samtstern Jul 16, 2020
60381b3
Fix dev dependency
samtstern Jul 17, 2020
318ecca
Fix how env is checked
samtstern Jul 17, 2020
6b85198
var name
samtstern Jul 17, 2020
98c993f
Merge remote-tracking branch 'origin/master' into ss-fork-testing
samtstern Aug 3, 2020
a7351d8
Deps
samtstern Aug 3, 2020
f4864f0
Update Firestore dep
samtstern Aug 3, 2020
d1ac304
Fix yarn.lock
samtstern Aug 4, 2020
ab2352b
Make tests pass
samtstern Aug 4, 2020
b431203
Merge remote-tracking branch 'origin/master' into ss-fork-testing
samtstern Aug 4, 2020
8f54ed5
Add changeset
samtstern Aug 4, 2020
3e2b499
Update versions and changeset
samtstern Aug 6, 2020
2b92d40
Merge remote-tracking branch 'origin/master' into ss-fork-testing
samtstern Aug 7, 2020
f9d9898
Merge remote-tracking branch 'origin/master' into ss-fork-testing
samtstern Aug 10, 2020
c5b7268
Restore testing package (#3556)
samtstern Aug 11, 2020
d9bdde6
Update changeset ignore config
samtstern Aug 11, 2020
9ee4bf9
Revert some changes
samtstern Aug 11, 2020
23fc04a
Revert some changes
samtstern Aug 11, 2020
be695ee
No changelog
samtstern Aug 11, 2020
186908f
Merge branch 'master' into ss-fork-testing
samtstern Aug 12, 2020
9993f0d
Update package.json
samtstern Aug 12, 2020
cb12261
No port conflict
samtstern Aug 12, 2020
3b0c125
Merge remote-tracking branch 'origin/master' into ss-fork-testing
samtstern Aug 12, 2020
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ packages/auth @bojeil-google @avolkovi @samhorlbeck @scottcrossen @firebase/jss
packages/auth-types @bojeil-google @avolkovi @samhorlbeck @scottcrossen @firebase/jssdk-global-approvers

# Testing Code
packages/testing @avolkovi @samhorlbeck @scottcrossen @yuchenshi @firebase/jssdk-global-approvers
packages/rules-unit-testing @avolkovi @samhorlbeck @scottcrossen @yuchenshi @firebase/jssdk-global-approvers

# RxFire Code
packages/rxfire @davideast @jamesdaniels @firebase/jssdk-global-approvers
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# @firebase/testing
# @firebase/rules-unit-testing

A set of utilities useful for testing Security Rules with the Realtime Database or Cloud Firestore
emulators.

See:

* [Test your Cloud Firestore Security Rules](https://firebase.google.com/docs/firestore/security/test-rules-emulator)
* [Testing Security Rules with the Realtime Database Emulator](https://firebase.google.com/docs/database/security/test-rules-emulator)
* [Testing Security Rules with the Realtime Database Emulator](https://firebase.google.com/docs/database/security/test-rules-emulator)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/*
* The testing module does not need to be registered since it should not ever
* come by default. The only way to use the testing module is by explicitly
* creating a dependency on @firebase/testing.
* creating a dependency on @firebase/rules-unit-testing.
*/

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@firebase/testing",
"version": "0.20.6",
"name": "@firebase/rules-unit-testing",
"version": "0.30.0",
"description": "",
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
"main": "dist/index.cjs.js",
Expand All @@ -10,7 +10,7 @@
"files": ["dist"],
"scripts": {
"build": "rollup -c",
"build:deps": "lerna run --scope @firebase/testing --include-dependencies build",
"build:deps": "lerna run --scope @firebase/rules-unit-testing --include-dependencies build",
"dev": "rollup -c -w",
"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",
"test": "firebase emulators:exec 'yarn test:nyc'",
Expand All @@ -26,12 +26,16 @@
},
"devDependencies": {
"@types/request": "2.48.5",
"firebase-tools": "8.5.0",
"firebase-tools": "^8.5.0",
"firebase-admin": "^9.0.0",
"rollup": "2.21.0",
"rollup-plugin-typescript2": "0.27.1"
},
"peerDependencies": {
"firebase-admin": "^9.0.0"
},
"repository": {
"directory": "packages/testing",
"directory": "packages/rules-unit-testing",
"type": "git",
"url": "https://github.com/firebase/firebase-js-sdk.git"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,21 @@ import { Component, ComponentType } from '@firebase/component';
export { database, firestore } from 'firebase';

/** If this environment variable is set, use it for the database emulator's address. */
const DATABASE_ADDRESS_ENV: string = 'FIREBASE_DATABASE_EMULATOR_ADDRESS';
const DATABASE_ADDRESS_ENV: string = 'FIREBASE_DATABASE_EMULATOR_HOST';
/** The default address for the local database emulator. */
const DATABASE_ADDRESS_DEFAULT: string = 'localhost:9000';
/** The actual address for the database emulator */
const DATABASE_ADDRESS: string =
process.env[DATABASE_ADDRESS_ENV] || DATABASE_ADDRESS_DEFAULT;

/** If any of environment variable is set, use it for the Firestore emulator. */
const FIRESTORE_ADDRESS_ENVS: string[] = [
'FIRESTORE_EMULATOR_HOST',
'FIREBASE_FIRESTORE_EMULATOR_ADDRESS'
];
const FIRESTORE_ADDRESS_ENV: string = 'FIRESTORE_EMULATOR_HOST';
/** The default address for the local Firestore emulator. */
const FIRESTORE_ADDRESS_DEFAULT: string = 'localhost:8080';

/** The actual address for the database emulator */
let _databaseHost: string | undefined = undefined;

/** The actual address for the Firestore emulator */
const FIRESTORE_ADDRESS: string = FIRESTORE_ADDRESS_ENVS.reduce(
(addr, name) => process.env[name] || addr,
FIRESTORE_ADDRESS_DEFAULT
);
let _firestoreHost: string | undefined = undefined;

/** Passing this in tells the emulator to treat you as an admin. */
const ADMIN_TOKEN = 'owner';
/** Create an unsecured JWT for the given auth payload. See https://tools.ietf.org/html/rfc7519#section-6. */
function createUnsecuredJwt(auth: object): string {
// Unsecured JWTs use "none" as the algorithm.
Expand Down Expand Up @@ -95,23 +88,84 @@ export type AdminAppOptions = {
};
/** Construct an App authenticated as an admin user. */
export function initializeAdminApp(options: AdminAppOptions): firebase.app.App {
return initializeApp(ADMIN_TOKEN, options.databaseName, options.projectId);
const admin = require('firebase-admin');

const app = admin.initializeApp(
getAppOptions(options.databaseName, options.projectId),
getRandomAppName()
);

if (options.projectId) {
app.firestore().settings({
host: getFirestoreHost(),
ssl: false
});
}

return app;
}

function initializeApp(
accessToken?: string,
function getDatabaseHost() {
if (!_databaseHost) {
const fromEnv = process.env[DATABASE_ADDRESS_ENV];
if (fromEnv) {
_databaseHost = fromEnv;
} else {
console.warn(
`Warning: ${DATABASE_ADDRESS_ENV} not set, using default value ${DATABASE_ADDRESS_DEFAULT}`
);
_databaseHost = DATABASE_ADDRESS_DEFAULT;
}
}

return _databaseHost;
}

function getFirestoreHost() {
if (!_firestoreHost) {
const fromEnv = process.env[FIRESTORE_ADDRESS_ENV];
if (fromEnv) {
_firestoreHost = fromEnv;
} else {
console.warn(
`Warning: ${FIRESTORE_ADDRESS_ENV} not set, using default value ${FIRESTORE_ADDRESS_DEFAULT}`
);
_firestoreHost = FIRESTORE_ADDRESS_DEFAULT;
}
}

return _firestoreHost;
}

function getRandomAppName(): string {
return 'app-' + new Date().getTime() + '-' + Math.random();
}

function getAppOptions(
databaseName?: string,
projectId?: string
): firebase.app.App {
): { [key: string]: string } {
let appOptions: { [key: string]: string } = {};

if (databaseName) {
appOptions['databaseURL'] = `http://${DATABASE_ADDRESS}?ns=${databaseName}`;
appOptions[
'databaseURL'
] = `http://${getDatabaseHost()}?ns=${databaseName}`;
}
if (projectId) {
appOptions['projectId'] = projectId;
}
const appName = 'app-' + new Date().getTime() + '-' + Math.random();
let app = firebase.initializeApp(appOptions, appName);

return appOptions;
}

function initializeApp(
accessToken?: string,
databaseName?: string,
projectId?: string
): firebase.app.App {
const appOptions = getAppOptions(databaseName, projectId);
const app = firebase.initializeApp(appOptions, getRandomAppName());
if (accessToken) {
const mockAuthComponent = new Component(
'auth-internal',
Expand Down Expand Up @@ -141,7 +195,7 @@ function initializeApp(
}
if (projectId) {
app.firestore().settings({
host: FIRESTORE_ADDRESS,
host: getFirestoreHost(),
ssl: false
});
}
Expand Down Expand Up @@ -171,7 +225,9 @@ export function loadDatabaseRules(
return new Promise((resolve, reject) => {
request.put(
{
uri: `http://${DATABASE_ADDRESS}/.settings/rules.json?ns=${options.databaseName}`,
uri: `http://${getDatabaseHost()}/.settings/rules.json?ns=${
options.databaseName
}`,
headers: { Authorization: 'Bearer owner' },
body: options.rules
},
Expand Down Expand Up @@ -206,7 +262,9 @@ export function loadFirestoreRules(
return new Promise((resolve, reject) => {
request.put(
{
uri: `http://${FIRESTORE_ADDRESS}/emulator/v1/projects/${options.projectId}:securityRules`,
uri: `http://${getFirestoreHost()}/emulator/v1/projects/${
options.projectId
}:securityRules`,
body: JSON.stringify({
rules: {
files: [{ content: options.rules }]
Expand Down Expand Up @@ -240,7 +298,9 @@ export function clearFirestoreData(
return new Promise((resolve, reject) => {
request.delete(
{
uri: `http://${FIRESTORE_ADDRESS}/emulator/v1/projects/${options.projectId}/databases/(default)/documents`,
uri: `http://${getFirestoreHost()}/emulator/v1/projects/${
options.projectId
}/databases/(default)/documents`,
body: JSON.stringify({
database: `projects/${options.projectId}/databases/(default)`
})
Expand All @@ -261,9 +321,23 @@ export function clearFirestoreData(

export function assertFails(pr: Promise<any>): any {
return pr.then(
v =>
Promise.reject(new Error('Expected request to fail, but it succeeded.')),
err => err
(v: any) => {
return Promise.reject(
new Error('Expected request to fail, but it succeeded.')
);
},
(err: any) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now make sure the error is a FirebaseError with a message that contains PERMISSION_DENIED. This prevents connect failures from giving the illusion of security.

const isPermissionDenied =
err && err.message && err.message.indexOf('PERMISSION_DENIED') >= 0;
if (!isPermissionDenied) {
return Promise.reject(
new Error(
`Expected PERMISSION_DENIED but got unexpected error: ${err}`
)
);
}
return err;
}
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,29 @@ describe('Testing Module Tests', function () {
.catch(() => {});
});

it('assertFails() iff failure', async function () {
it('assertFails() iff PERMISSION_DENIED', async function () {
const success = Promise.resolve('success');
const failure = Promise.reject('failure');
const permissionDenied = Promise.reject({
message: 'PERMISSION_DENIED'
});
const otherFailure = Promise.reject('failure');
await firebase
.assertFails(success)
.then(() => {
throw new Error('Expected success to fail.');
})
.catch(() => {});
await firebase.assertFails(failure).catch(() => {
throw new Error('Expected failure to succeed.');

await firebase.assertFails(permissionDenied).catch(() => {
throw new Error('Expected permissionDenied to succeed.');
});

await firebase
.assertFails(otherFailure)
.then(() => {
throw new Error('Expected otherFailure to fail.');
})
.catch(() => {});
});

it('initializeTestApp() with auth=null does not set access token', async function () {
Expand Down
Loading