-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #150 from a-xin/add-jest-matcher
Add jest matcher
- Loading branch information
Showing
11 changed files
with
509 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
|
||
## Using Targaryen with Jest | ||
|
||
1. Run `npm install -g jest` and `npm install --save-dev targaryen`. | ||
|
||
2. Create a new directory for your security tests (**NOTE**: Jest defaults to look for tests inside of `__tests__` folders, or in files that end in `.spec.js` or `.test.js`). | ||
|
||
3. Add a new *fixture JSON* for the state of your Firebase. Call this `spec/security/<firebase path>.json`. This file will describe the state of the Firebase data store for your tests, that is, what you can get via the `root` and `data` variables in the security rules. | ||
|
||
4. Create a new file for your first set of tests, like `spec/security/<firebase path>.spec.js`. | ||
|
||
5. Add the following content to the top of the new file: | ||
|
||
```js | ||
// user-rules.spec.js | ||
const targaryen = require('targaryen/plugins/jest'); | ||
|
||
expect.extend({ | ||
toAllowRead: targaryen.toAllowRead, | ||
toAllowUpdate: targaryen.toAllowUpdate, | ||
toAllowWrite: targaryen.toAllowWrite, | ||
}); | ||
|
||
const RULES_PATH = 'database.rules.json'; | ||
const rules = targaryen.json.loadSync(RULES_PATH); | ||
const initialData = require(path.join(__dirname, path.basename(__filename, '.spec.js') + '.json')); | ||
|
||
test('basic', () => { | ||
const database = targaryen.getDatabase(rules, initialData); | ||
|
||
expect(database.as(targaryen.users.unauthenticated)).not.toAllowRead('/user'); | ||
expect(database.as(targaryen.users.unauthenticated)).toAllowRead('/public'); | ||
expect(database.as(targaryen.users.facebook)).toAllowRead('/user'); | ||
expect(database.as({ uid: '1234'})).toAllowWrite('/user/1234', { | ||
name: 'Anna', | ||
}); | ||
}); | ||
``` | ||
|
||
where `RULES_PATH` is the path to your security rules JSON file. If your security rules are broken, Targaryen will throw an exception at this point with detailed information about what specifically is broken. | ||
|
||
6. Write your security tests. | ||
|
||
The subject of every assertion will be the authentication state (i.e., `auth`) of the user trying the operation, so for instance, `null` would be an unauthenticated user, or a Firebase Password Login user would look like `{ uid: 'password:500f6e96-92c6-4f60-ad5d-207253aee4d3', id: 1, provider: 'password' }`. There are symbolic versions of these in `targaryen.users`. | ||
|
||
See the API section below for details, or take a look at the example files here. | ||
|
||
7. Run the tests with `jest`. | ||
|
||
## API | ||
|
||
- import with `require('targaryen/plugins/jest')`. | ||
- `jestTargaryen.toAllowRead`, `jestTargaryen.toAllowWrite`, `jestTargaryen.toAllowUpdate`, `jestTargaryen.toBeAllowed`: The jest matchers. Load them using `expect.extend({toAllowRead: targaryen.toAllowRead, toAllowWrite: targaryen.toAllowWrite, toAllowUpdate: targaryen.toAllowUpdate, toBeAllowed: targaryen.toBeAllowed});` before running any tests. | ||
- `jestTargaryen.getDatabase(rules: object|Ruleset, data: object|DataNode, now: null|number): Database`: Wrapper for `targaryen.database()`. | ||
- `jestTargaryen.getDebugDatabase(rules: object|Ruleset, data: object|DataNode, now: null|number): Database`: Wrapper for `targaryen.database()` that also enables debug mode. Use this if you write your tests using the generic matcher `toBeAllowed()`. | ||
- `jestTargaryen.json`: Export of `firebase-json` to allow parsing of firebase rule files. | ||
- `jestTargaryen.users`: A set of authentication objects you can use as the subject of the assertions. Has the following keys: | ||
- `unauthenticated`: an unauthenticated user, i.e., `auth === null`. | ||
- `anonymous`: a user authenticated using Firebase anonymous sessions. | ||
- `password`: a user authenticated using Firebase Password Login. | ||
- `facebook`: a user authenticated by their Facebook account. | ||
- `twitter`: a user authenticated by their Twitter account. | ||
- `google`: a user authenticated by their Google account. | ||
- `github`: a user authenticated by their Github account. | ||
- `expect(auth).canRead(path: string [, options: {now?: number, query?: object} ])`: asserts that the given path is readable by a user with the given authentication data. | ||
- `expect(auth).cannotRead(path: string[, options: {now?: number, query?: object} ])`: asserts that the given path is not readable by a user with the given authentication data. | ||
- `expect(auth).canWrite(path: string [, data: any [, options: {now: number, priority: any} ]])`: asserts that the given path is writable by a user with the given authentication data. Optionally takes a Javascript object containing `newData`, otherwise this will be set to `null`. | ||
- `expect(auth).cannotWrite(path: string [, data: any [, options: {now: number, priority: any} ]])`: asserts that the given path is not writable by a user with the given authentication data. Optionally takes a Javascript object containing `newData`, otherwise this will be set to `null`. | ||
- `expect(auth).canPatch(path: string, patch: {[path: string]: any} [, options: {now: number} ])`: asserts that the given patch (or multi-location update) operation is writable by a user with the given authentication data. | ||
- `expect(auth).cannotPatch(path: string, patch: {[path: string]: any} [, options: {now: number} ])`: asserts that the given patch (or multi-location update) operation is writable by a user with the given authentication data. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ | |
"prepare": "npm run build", | ||
"test": "npm run build && mocha -r test/setup.js test/spec/ --recursive && jasmine && node ./bin/targaryen --verbose test/integration/rules.json test/integration/tests.json", | ||
"test:unit": "mocha -r test/setup.js test/spec/ --recursive", | ||
"test:plugin:jest": "jest test/jest", | ||
"test:inspect": "node --inspect --debug-brk node_modules/.bin/_mocha -r test/setup.js test/spec/ --recursive" | ||
}, | ||
"author": "Harry Schmidt <[email protected]>", | ||
|
@@ -72,9 +73,11 @@ | |
"coveralls": "^2.11.15", | ||
"eslint": "^3.9.1", | ||
"eslint-config-xo": "^0.17.0", | ||
"eslint-plugin-jest": "^21.15.1", | ||
"eslint-plugin-node": "^2.1.3", | ||
"istanbul": "^0.4.5", | ||
"jasmine": "^2.1.1", | ||
"jest": "^22.4.3", | ||
"mocha": "^2.1.0", | ||
"sinon": "^1.17.6", | ||
"sinon-chai": "^2.8.0" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/** | ||
* targaryen/plugins/jest - Reference implementation of a jest plugin for | ||
* targaryen. | ||
* | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const json = require('firebase-json'); | ||
const targaryen = require('../'); | ||
|
||
// Need to disable eslint rule for jest's utils: this.utils.EXPECTED_COLOR('a') | ||
/* eslint-disable new-cap */ | ||
|
||
function toBeAllowed(result) { | ||
const pass = result.allowed === true; | ||
const message = pass ? | ||
() => `Expected operation to be ${this.utils.EXPECTED_COLOR('denied')} but it was ${this.utils.RECEIVED_COLOR('allowed')}\n\n${result.info}` : | ||
() => `Expected operation to be ${this.utils.EXPECTED_COLOR('allowed')} but it was ${this.utils.RECEIVED_COLOR('denied')}\n\n${result.info}`; | ||
|
||
return { | ||
message, | ||
pass | ||
}; | ||
} | ||
|
||
function toAllowRead(database, path, options) { | ||
const result = database | ||
.with({debug: true}) | ||
.read(path, options); | ||
|
||
const pass = result.allowed === true; | ||
const message = pass ? | ||
() => `Expected ${this.utils.EXPECTED_COLOR('read')} to be ${this.utils.EXPECTED_COLOR('denied')} but it was ${this.utils.RECEIVED_COLOR('allowed')}\n\n${result.info}` : | ||
() => `Expected ${this.utils.EXPECTED_COLOR('read')} to be ${this.utils.EXPECTED_COLOR('allowed')} but it was ${this.utils.RECEIVED_COLOR('denied')}\n\n${result.info}`; | ||
|
||
return { | ||
message, | ||
pass | ||
}; | ||
} | ||
|
||
function toAllowWrite(database, path, value, options) { | ||
const result = database | ||
.with({debug: true}) | ||
.write(path, value, options); | ||
|
||
const pass = result.allowed === true; | ||
const message = pass ? | ||
() => `Expected ${this.utils.EXPECTED_COLOR('write')} to be ${this.utils.EXPECTED_COLOR('denied')} but it was ${this.utils.RECEIVED_COLOR('allowed')}\n\n${result.info}` : | ||
() => `Expected ${this.utils.EXPECTED_COLOR('write')} to be ${this.utils.EXPECTED_COLOR('allowed')} but it was ${this.utils.RECEIVED_COLOR('denied')}\n\n${result.info}`; | ||
|
||
return { | ||
message, | ||
pass | ||
}; | ||
} | ||
|
||
function toAllowUpdate(database, path, patch, options) { | ||
const result = database | ||
.with({debug: true}) | ||
.update(path, patch, options); | ||
|
||
const pass = result.allowed === true; | ||
const message = pass ? | ||
() => `Expected ${this.utils.EXPECTED_COLOR('update')} to be ${this.utils.EXPECTED_COLOR('denied')} but it was ${this.utils.RECEIVED_COLOR('allowed')}\n\n${result.info}` : | ||
() => `Expected ${this.utils.EXPECTED_COLOR('update')} to be ${this.utils.EXPECTED_COLOR('allowed')} but it was ${this.utils.RECEIVED_COLOR('denied')}\n\n${result.info}`; | ||
|
||
return { | ||
message, | ||
pass | ||
}; | ||
} | ||
|
||
/** | ||
* Expose `targaryen.database()` for conveniently creating a | ||
* database for a jest test. | ||
* | ||
* @return {Database} | ||
*/ | ||
const getDatabase = targaryen.database; | ||
|
||
/** | ||
* Simple wrapper for `targaryen.database()` that also enables debug mode for | ||
* detailed error messages. | ||
* | ||
* @return {Database} | ||
*/ | ||
function getDebugDatabase() { | ||
return targaryen.database.apply(this, arguments).with({debug: true}); | ||
} | ||
|
||
const jestTargaryen = { | ||
toBeAllowed, | ||
toAllowRead, | ||
toAllowWrite, | ||
toAllowUpdate, | ||
|
||
// NOTE: Exported for convenience only | ||
getDatabase, | ||
getDebugDatabase, | ||
json, | ||
users: targaryen.util.users | ||
}; | ||
|
||
module.exports = jestTargaryen; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
extends: '../../.eslintrc.yml' | ||
plugins: | ||
- jest | ||
env: | ||
jest/globals: true | ||
rules: | ||
jest/no-disabled-tests: warn | ||
jest/no-focused-tests: error | ||
jest/no-identical-title: error | ||
jest/valid-expect: error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`generic matchers toBeAllowed 1`] = ` | ||
"Expected operation to be [32mallowed[39m but it was [31mdenied[39m | ||
Attempt to read /user as null. | ||
/user: read <no rules> | ||
No .read rule allowed the operation. | ||
read was denied." | ||
`; | ||
exports[`generic matchers toBeAllowed 2`] = ` | ||
"Expected operation to be [32mdenied[39m but it was [31mallowed[39m | ||
Attempt to write /user/1234 as {\\"uid\\":\\"1234\\"}. | ||
New Value: \\"{ | ||
\\"name\\": \\"Anna\\" | ||
}\\". | ||
/user/1234: write \\"auth.uid === $uid\\" => true | ||
auth.uid === $uid [=> true] | ||
using [ | ||
$uid = \\"1234\\" | ||
auth = {\\"uid\\":\\"1234\\"} | ||
auth.uid = \\"1234\\" | ||
] | ||
write was allowed." | ||
`; |
Oops, something went wrong.