Compare commits

..
617 Commits
Author SHA1 Message Date
Tom Fox b96087ec61 3.7.0 version bump + changelog (#5854)
* 3.7.0 version bump + changelog

* Update CHANGELOG.md
2019-07-28 23:46:13 -07:00
Ivan SZKIBA dfe0ff753c support PhantAuth authentication (#5850)
* support PhantAuth authentication

* fix spelling issues

* Add test case
2019-07-29 00:58:43 -05:00
greenkeeper[bot] d810f34cc5 Update parse to the latest version 🚀 (#5862)
* fix(package): update parse to version 2.6.0

* chore(package): update lockfile package-lock.json
2019-07-29 05:14:44 +00:00
Diamond Lewis 95208e96e0 Postgres: Safely escape strings in nested objects (#5855)
* Postgres: Safely handle string in nested objects

* fix failing tests
2019-07-28 23:54:13 -05:00
Zeal Murapa 4f21c36825 Update ParseWebSocketServer.js (#5860)
* Update ParseWebSocketServer.js

fix wss:// error by requiring 'ws' module, remove uws as it has been deprecated and removed from npm

* Update ParseWebSocketServer.js

* remove uws
2019-07-28 22:17:09 -05:00
greenkeeper[bot] 1903f59db5 Update eslint-plugin-flowtype to the latest version 🚀 (#5858)
* chore(package): update eslint-plugin-flowtype to version 3.12.2

* chore(package): update lockfile package-lock.json
2019-07-29 00:35:51 +00:00
Diamond Lewis f1619e428e Add git to Dockerfile (#5861) 2019-07-28 16:52:19 -05:00
greenkeeper[bot] 80bb2b6389 Update pg-promise to the latest version 🚀 (#5857)
* fix(package): update pg-promise to version 9.0.0

* chore(package): update lockfile package-lock.json

* remove es6 generators
2019-07-27 19:07:36 +00:00
greenkeeper[bot] 8c321ed750 Update apollo-server to the latest version 🚀 (#5856)
* fix(package): update apollo-server-express to version 2.7.2

* chore(package): update lockfile package-lock.json
2019-07-26 23:56:24 +00:00
greenkeeper[bot] 601a6ff788 Update @parse/push-adapter to the latest version 🚀 (#5852)
* fix(package): update @parse/push-adapter to version 3.0.5

* chore(package): update lockfile package-lock.json

* Update flow-bin to the latest version 🚀 (#5853)

* chore(package): update flow-bin to version 0.104.0

* chore(package): update lockfile package-lock.json

* Update package.json

* Pin to 3.0.8
2019-07-26 22:33:14 +00:00
greenkeeper[bot] 7eadf35840 Update flow-bin to the latest version 🚀 (#5853)
* chore(package): update flow-bin to version 0.104.0

* chore(package): update lockfile package-lock.json
2019-07-26 20:43:47 +00:00
Antonio Davi Macedo Coelho de Castro fd637ff4f8 Required fields and default values (#5835)
* Add field options to mongo schema metadata

* Add/fix test with fields options

* Add required validation failing test

* Add more tests

* Only set default value if field is undefined

* Fix redis test

* Fix tests

* Test for creating a new class with field options

* Validate default value type

* fix lint (weird)

* Fix lint another way

* Add tests for beforeSave trigger and solve small issue regarding the use of unset in the beforeSave trigger
2019-07-25 21:13:59 -07:00
Omair Vaiyani d3810c2eba GraphQL Configuration Options (#5782)
* add parse-graph-ql configuration for class schema customisation

Not yet tested - essentially an RFC

* refactor and add graphql router, controller and config cache

* fix(GraphQLController): add missing check isEnabled

* chore(GraphQLController): remove awaits from cache put

* chore(GraphQLController): remove check for if its enabled

* refactor(GraphQLController): only use cache if mounted

* chore(GraphQLController): group all validation errors and throw at once

* chore(GraphQLSchema): move transformations into controller validation

* refactor(GraphQL): improve ctrl validation and fix schema usage of config

* refactor(GraphQLSchema): remove code related to additional schema

This code has been moved into a separate feature branch.

* fix(GraphQLSchema): fix incorrect default return type for class configs

* refactor(GraphQLSchema): update staleness check code to account for config

* fix(GraphQLServer): fix regressed tests due to internal schema changes

This will be followed up with a backwards compatability fix for the `ClassFields` issue to avoid breakages for our users

* refactor: rename to ParseGraphQLController for consistency

* fix(ParseGraphQLCtrl): numerous fixes for validity checking

Also includes some minor code refactoring

* chore(GraphQL): minor syntax cleanup

* fix(SchemaController): add _GraphQLConfig to volatile classes

* refactor(ParseGraphQLServer): return update config value in setGraphQLConfig

* testing(ParseGraphQL): add test cases for new graphQLConfig

* fix(GraphQLController): fix issue where config with multiple items was not being mapped to the db

* fix(postgres): add _GraphQLConfig default schema on load

fixes failing postgres tests

* GraphQL @mock directive (#5836)

* Add mock directive
* Include tests for @mock directive

* Fix existing tests due to the change from ClassFields to ClassCreateFields

* fix(parseClassMutations): safer type transformation based on input type

* fix(parseClassMutations): only define necessary input fields

* fix(GraphQL): fix incorrect import paths
2019-07-25 12:46:25 -07:00
dependabot-preview[bot] bbcc20fd60 Bump apollo-server-express from 2.7.0 to 2.7.1 (#5847)
Bumps [apollo-server-express](https://github.com/apollographql/apollo-server) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/apollographql/apollo-server/releases)
- [Changelog](https://github.com/apollographql/apollo-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-server/compare/apollo-server-express@2.7.0...apollo-server-express@2.7.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-25 18:33:03 +00:00
dependabot-preview[bot] c475a14408 Bump lint-staged from 9.2.0 to 9.2.1 (#5846)
* Bump lint-staged from 9.2.0 to 9.2.1

Bumps [lint-staged](https://github.com/okonet/lint-staged) from 9.2.0 to 9.2.1.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v9.2.0...v9.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* remove flaky test

* re-add test
2019-07-25 18:28:18 +00:00
Antonio Davi Macedo Coelho de Castro 8a925177e1 Cache apple public key for the case it fails to fetch again (#5848) 2019-07-25 10:20:28 -07:00
BrunoMaurice 50f1e8eb77 Make possible to alter response using the after save trigger (#5814)
* make possible to alter response using the after save trigger like for after find

* code clearing to follow same object checking

* remove console log debug

* fix test unit
2019-07-25 09:31:18 -07:00
Douglas Muraoka 4fe0ff6ebf feat: Add "count" to CLP initial value (#5841)
* feat: count CLP default values

* fix tests
2019-07-24 12:41:18 -05:00
greenkeeper[bot] a5fb38b0b2 Update semver to the latest version 🚀 (#5843)
* fix(package): update semver to version 6.3.0

* chore(package): update lockfile package-lock.json
2019-07-23 19:51:04 +00:00
Antonio Davi Macedo Coelho de Castro b605638415 Fix: GraphQL _or operator not working (#5840) 2019-07-23 10:29:38 -03:00
macarthurorandTom Fox 5398b6f667 custom pages example and an email adapter is added (#5838)
* custom pages example and an email adapter is added

* npm link of email adapter

* spelling and grammar changes

Co-Authored-By: Tom Fox <tomfox@surprises.io>

* spelling changes

Co-Authored-By: Tom Fox <tomfox@surprises.io>
2019-07-23 10:16:41 +01:00
Antonio Davi Macedo Coelho de Castro 2e0940c996 GraphQL @mock directive (#5836)
* Add mock directive
* Include tests for @mock directive
2019-07-22 15:19:40 -03:00
greenkeeper[bot] f336cc3435 Update eslint to the latest version 🚀 (#5832)
* chore(package): update eslint to version 6.1.0

* chore(package): update lockfile package-lock.json
2019-07-21 04:59:38 +00:00
greenkeeper[bot] f03fa6bcf9 Update pg-promise to the latest version 🚀 (#5831)
* fix(package): update pg-promise to version 8.7.5

* chore(package): update lockfile package-lock.json
2019-07-20 01:24:10 +00:00
Antonio Davi Macedo Coelho de CastroandTom Fox a6f441248b GraphQL custom schema on CLI (#5828)
* Add --graphQLSchema to CLI

* Add custom graphql schema instructions to readme file

* Update README.md

Co-Authored-By: Tom Fox <tomfox@surprises.io>

* Update src/Options/Definitions.js

Co-Authored-By: Tom Fox <tomfox@surprises.io>

* Update src/Options/docs.js

Co-Authored-By: Tom Fox <tomfox@surprises.io>

* Update src/Options/index.js

Co-Authored-By: Tom Fox <tomfox@surprises.io>
2019-07-19 12:29:45 -07:00
greenkeeper[bot] 6d5f6b4c94 Update ws to the latest version 🚀 (#5830)
* fix(package): update ws to version 7.1.1

* chore(package): update lockfile package-lock.json
2019-07-19 15:14:58 +00:00
greenkeeper[bot] 7c809f5627 Update lodash to the latest version 🚀 (#5827)
* fix(package): update lodash to version 4.17.15

* chore(package): update lockfile package-lock.json
2019-07-19 03:06:22 +00:00
greenkeeper[bot] c57f0d329b Update eslint-plugin-flowtype to the latest version 🚀 (#5826)
* chore(package): update eslint-plugin-flowtype to version 3.12.1

* chore(package): update lockfile package-lock.json
2019-07-19 00:01:24 +00:00
greenkeeper[bot] beeb097e55 Update husky to the latest version 🚀 (#5825)
* chore(package): update husky to version 3.0.1

* chore(package): update lockfile package-lock.json
2019-07-18 22:11:48 +00:00
greenkeeper[bot] 61ae2eb91b Update flow-bin to the latest version 🚀 (#5824)
* chore(package): update flow-bin to version 0.103.0

* chore(package): update lockfile package-lock.json
2019-07-18 21:03:33 +00:00
Antonio Davi Macedo Coelho de Castro 71d92aed8d GraphQL Custom Schema (#5821)
This PR empowers the Parse GraphQL API with custom user-defined schema. The developers can now write their own types, queries, and mutations, which will merged with the ones that are automatically generated. The new types are resolved by the application's cloud code functions.

Therefore, regarding https://github.com/parse-community/parse-server/issues/5777, this PR closes the cloud functions needs and also addresses the graphql customization topic. In my view, I think that this PR, together with https://github.com/parse-community/parse-server/pull/5782 and https://github.com/parse-community/parse-server/pull/5818, when merged, closes the issue.

How it works:

1. When initializing ParseGraphQLServer, now the developer can pass a custom schema that will be merged to the auto-generated one:
```
      parseGraphQLServer = new ParseGraphQLServer(parseServer, {
        graphQLPath: '/graphql',
        graphQLCustomTypeDefs: gql`
          extend type Query {
            custom: Custom @namespace
          }
           type Custom {
            hello: String @resolve
            hello2: String @resolve(to: "hello")
            userEcho(user: _UserFields!): _UserClass! @resolve
          }
        `,
      });
```

Note:
- This PR includes a @namespace directive that can be used to the top level field of the nested queries and mutations (it basically just returns an empty object);
- This PR includes a @resolve directive that can be used to notify the Parse GraphQL Server to resolve that field using a cloud code function. The `to` argument specifies the function name. If the `to` argument is not passed, the Parse GraphQL Server will look for a function with the same name of the field;
- This PR allows creating custom types using the auto-generated ones as in `userEcho(user: _UserFields!): _UserClass! @resolve`;
- This PR allows to extend the auto-generated types, as in `extend type Query { ... }`.

2. Once the schema was set, you just need to write regular cloud code functions:
```
      Parse.Cloud.define('hello', async () => {
        return 'Hello world!';
      });

      Parse.Cloud.define('userEcho', async req => {
        return req.params.user;
      });
```

3. Now you are ready to play with your new custom api:
```
query {
  custom {
    hello
    hello2
    userEcho(user: { username: "somefolk" }) {
      username
    }
  }
}
```
should return
```
{
  "data": {
    "custom": {
      "hello": "Hello world!",
      "hello2": "Hello world!",
      "userEcho": {
        "username": "somefolk"
      }
    }
  }
}
```
2019-07-18 16:43:49 -03:00
Antonio Davi Macedo Coelho de Castro 0b86a86209 GraphQL { functions { call } } generic mutation (#5818)
* Generic call function mutation
* Change function return type to any
* First passing test
* Testing errors
* Testing different data types
2019-07-18 16:41:10 -03:00
greenkeeper[bot] 6be15331a6 Update eslint-plugin-flowtype to the latest version 🚀 (#5823)
* chore(package): update eslint-plugin-flowtype to version 3.12.0

* chore(package): update lockfile package-lock.json
2019-07-18 17:14:07 +00:00
Tom Fox 516d789491 Add contributors section, improve backers section (#5815)
* add contributors, improve backers section

* spacing
2019-07-18 17:47:16 +01:00
greenkeeper[bot] 9eec0f1b78 Update babel7 to the latest version 🚀 (#5820)
* chore(package): update @babel/cli to version 7.5.5

* chore(package): update @babel/core to version 7.5.5

* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.5.5

* chore(package): update @babel/preset-env to version 7.5.5

* chore(package): update lockfile package-lock.json
2019-07-17 23:40:20 +00:00
greenkeeper[bot] 1b5c045706 Update apollo-server to the latest version 🚀 (#5817)
* fix(package): update apollo-server-express to version 2.7.0

* chore(package): update lockfile package-lock.json
2019-07-16 14:18:14 +00:00
greenkeeper[bot] 82e24cb18d Update @parse/s3-files-adapter to the latest version 🚀 (#5816)
* fix(package): update @parse/s3-files-adapter to version 1.2.3

* chore(package): update lockfile package-lock.json
2019-07-15 18:07:19 +00:00
greenkeeper[bot] cdc6eee295 Update jsdoc to the latest version 🚀 (#5813)
* chore(package): update jsdoc to version 3.6.3

* chore(package): update lockfile package-lock.json
2019-07-14 18:32:19 +00:00
Antonio Davi Macedo Coelho de Castro 7222bb6af9 Running GraphQL (#5810)
* Improving GraphQL running instruction

* Improving GraphQL learning more section

* Fixing typo
2019-07-13 21:01:32 -07:00
Douglas Muraoka f91034ab8c GraphQL: Improve session token error messages (#5753)
* GraphQL: Improve session token error message

Fixes the session token related error messages during GraphQL operations. If any authentication error were thrown, it was not correctly handled by the GraphQL express middleware, and ended responding the request with a JSON parsing error.

* Refactor handleError usage

* Use handleParseErrors middleware to handle invalid session token error

* fix: Status code 400 when session token is invalid

* fix: Undo handleParseErrors middleware change
2019-07-12 13:58:47 -07:00
greenkeeper[bot] 1c62ab6f49 Update winston-daily-rotate-file to the latest version 🚀 (#5808)
* fix(package): update winston-daily-rotate-file to version 3.10.0

* chore(package): update lockfile package-lock.json
2019-07-12 15:52:20 +00:00
greenkeeper[bot] 3e888249cc Update parse to the latest version 🚀 (#5806)
* fix(package): update parse to version 2.5.1

* chore(package): update lockfile package-lock.json
2019-07-12 00:42:32 +00:00
greenkeeper[bot] 527662f336 Update pg-promise to the latest version 🚀 (#5805)
* fix(package): update pg-promise to version 8.7.4

* chore(package): update lockfile package-lock.json
2019-07-11 22:46:36 +00:00
greenkeeper[bot] 0dc7152681 Update apollo-server to the latest version 🚀 (#5804)
* fix(package): update apollo-server-express to version 2.6.9

* chore(package): update lockfile package-lock.json
2019-07-11 19:02:20 +00:00
Diamond Lewis bb06376a32 Prevent linkWith sessionToken from generating new session (#5801) 2019-07-11 09:32:11 -05:00
Arthur Cinader 26943de778 Prepare 3.6.0 Release (#5792)
* Re-apply "Prepare for 3.6.0 Release""

This reverts commit 08dbafe49a.

* fix link formatting

* revert mongodb-runner upgrade.

* Update CHANGELOG.md

* Fix formatting
2019-07-11 09:22:36 -04:00
dependabot-preview[bot] 2afaf00d1f Bump lodash from 4.17.13 to 4.17.14 (#5803)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.13 to 4.17.14.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.13...4.17.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-11 11:22:24 +00:00
dependabot-preview[bot] b5b718196b Bump lint-staged from 9.1.0 to 9.2.0 (#5802)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 9.1.0 to 9.2.0.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v9.1.0...v9.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-11 11:13:41 +00:00
Diamond Lewis 5341b8248f Generate sessionToken with linkWith (#5799)
* Generate sessionToken with linkWith

* improve test

* Add comment
2019-07-10 20:23:16 +00:00
Raschid J.F. Rafeally 9816285205 Added rest option: excludeKeys (#5737)
* Added restOption: excludeKeys

* improve tests
2019-07-10 15:01:21 -05:00
Diamond Lewis 378e70afdc Fix #5794 (#5797) 2019-07-10 15:19:40 -04:00
Arthur Cinader 76ce9e1a5c Run test that require db access (#5796)
as mongo only.

also seperate out into own section of test.
2019-07-10 12:25:29 -05:00
Arthur Cinader 815b7c6e05 Too much output! (#5795)
Reducing the spew.
2019-07-10 11:56:04 -04:00
Diamond Lewis af6c44eca4 Handle LiveQuery create event with fields (#5790)
Close:  https://github.com/parse-community/parse-server/issues/5764

Fix logic handling null original object
2019-07-10 10:14:55 -05:00
Arthur Cinader 08dbafe49a Revert "Prepare for 3.6.0 Release"
This reverts commit 8c1124e3c4.
2019-07-10 10:21:47 -04:00
Arthur Cinader 8c1124e3c4 Prepare for 3.6.0 Release
also run 'npm audit fix' to address some dependency vulnerabilities
2019-07-10 10:18:29 -04:00
Fabian Strachanski 73b0f9a339 Merge pull request from GHSA-8w3j-g983-8jh5
* Add Test and Authenticator for ghsa-8w3j-g983-8jh5

* fix for ghsa-8w3j-g983-8jh5

* nit whitespace

not sure why lint isn't catching...
2019-07-10 09:47:23 -04:00
greenkeeper[bot] 050dd19e80 Update apollo-upload-client to the latest version 🚀 (#5791)
* chore(package): update apollo-upload-client to version 11.0.0

* chore(package): update lockfile package-lock.json
2019-07-10 01:12:46 +00:00
greenkeeper[bot] ed72e58ea6 Update apollo-server to the latest version 🚀 (#5789)
* fix(package): update apollo-server-express to version 2.6.8

* chore(package): update lockfile package-lock.json
2019-07-10 00:05:32 +00:00
greenkeeper[bot] bf74b7445e Update lodash to the latest version 🚀 (#5788)
* fix(package): update lodash to version 4.17.13

* chore(package): update lockfile package-lock.json
2019-07-09 23:22:57 +00:00
greenkeeper[bot] 60a9a4d79c Update lodash to the latest version 🚀 (#5787)
* fix(package): update lodash to version 4.17.12

* chore(package): update lockfile package-lock.json
2019-07-09 22:45:40 +00:00
greenkeeper[bot] 71ddeed2e7 Update babel7 to the latest version 🚀 (#5786)
* chore(package): update @babel/core to version 7.5.4

* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.5.4

* chore(package): update @babel/preset-env to version 7.5.4

* chore(package): update lockfile package-lock.json
2019-07-09 21:29:49 +00:00
greenkeeper[bot] 72dbc01712 Update babel7 to the latest version 🚀 (#5784)
* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.5.3

* chore(package): update @babel/preset-env to version 7.5.3

* chore(package): update lockfile package-lock.json
2019-07-09 18:25:27 +00:00
greenkeeper[bot] 04a196695a Update ws to the latest version 🚀 (#5781)
* fix(package): update ws to version 7.1.0

* chore(package): update lockfile package-lock.json
2019-07-08 23:18:46 -07:00
greenkeeper[bot] e471bb960d Update babel7 to the latest version 🚀 (#5783)
* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.5.2

* chore(package): update @babel/preset-env to version 7.5.2

* chore(package): update lockfile package-lock.json
2019-07-08 23:05:57 +00:00
greenkeeper[bot] f57646d397 Update lint-staged to the latest version 🚀 (#5776)
* chore(package): update lint-staged to version 9.1.0

* chore(package): update lockfile package-lock.json
2019-07-06 12:16:42 +00:00
greenkeeper[bot] cd350ee1d1 Update babel7 to the latest version 🚀 (#5775)
* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.5.1

* chore(package): update lockfile package-lock.json
2019-07-06 09:41:24 +00:00
dependabot-preview[bot] 180ec0090d Bump @babel/plugin-proposal-object-rest-spread from 7.4.4 to 7.5.0 (#5771)
Bumps [@babel/plugin-proposal-object-rest-spread](https://github.com/babel/babel) from 7.4.4 to 7.5.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.4...v7.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 19:08:37 +00:00
dependabot-preview[bot] 8918ac1598 Bump @babel/core from 7.4.5 to 7.5.0 (#5769)
Bumps [@babel/core](https://github.com/babel/babel) from 7.4.5 to 7.5.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.5...v7.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 11:46:25 -07:00
greenkeeper[bot] d35de8a12f Update @apollographql/graphql-playground-html to the latest version 🚀 (#5773)
* fix(package): update @apollographql/graphql-playground-html to version 1.6.24

* chore(package): update lockfile package-lock.json
2019-07-05 14:13:47 +00:00
greenkeeper[bot] fdf085f814 Update pg-promise to the latest version 🚀 (#5772)
* fix(package): update pg-promise to version 8.7.3

* chore(package): update lockfile package-lock.json
2019-07-05 13:57:40 +00:00
dependabot-preview[bot] a160bc33bf Bump @babel/cli from 7.4.4 to 7.5.0 (#5770)
Bumps [@babel/cli](https://github.com/babel/babel) from 7.4.4 to 7.5.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.4...v7.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 11:01:39 +00:00
dependabot-preview[bot] 7ea0b95d7b Bump @babel/preset-env from 7.4.5 to 7.5.0 (#5768)
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.4.5 to 7.5.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.5...v7.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 10:43:28 +00:00
dependabot-preview[bot] 0e05313a6a Bump @apollographql/graphql-playground-html from 1.6.20 to 1.6.23 (#5767)
Bumps [@apollographql/graphql-playground-html](https://github.com/graphcool/graphql-playground) from 1.6.20 to 1.6.23.
- [Release notes](https://github.com/graphcool/graphql-playground/releases)
- [Commits](https://github.com/graphcool/graphql-playground/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 10:33:51 +00:00
dependabot-preview[bot] 79e8123ffe Bump graphql from 14.4.1 to 14.4.2 (#5760)
Bumps [graphql](https://github.com/graphql/graphql-js) from 14.4.1 to 14.4.2.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v14.4.1...v14.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-04 10:41:25 +00:00
dependabot-preview[bot] 1761ab9046 Bump form-data from 2.4.0 to 2.5.0 (#5759)
Bumps [form-data](https://github.com/form-data/form-data) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Commits](https://github.com/form-data/form-data/compare/v2.4.0...v2.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-04 10:32:47 +00:00
Diamond Lewis 9900a956e6 ⚡️ Release 3.5.0 (#5754)
* ⚡️ Release 3.5.0

* nit
2019-07-03 17:07:49 -05:00
Diamond Lewis 3a7b0c4c75 Fix: Linking with Apple Auth (#5755)
Rename from apple-signin to apple (key names can't have hyphens
Rename id_token to id (auth adapters require id)
2019-07-03 16:28:29 -05:00
greenkeeper[bot] d33fb677be Update lint-staged to the latest version 🚀 (#5756)
* chore(package): update lint-staged to version 9.0.2

* chore(package): update lockfile package-lock.json
2019-07-03 06:23:53 +00:00
Douglas Muraoka 2c4031092e GraphQL: /me pointers not working (#5745)
When using the `/me` endpoint to fetch the current user, it does not fetches data from any Pointer data type field, even though the field was defined in the GraphQL schema.
2019-07-02 12:15:46 -07:00
Douglas Muraoka 3d63545ab7 GraphQL: User sign up required fields (#5743) 2019-07-02 12:11:45 -07:00
greenkeeper[bot] 60d9327a78 Update lint-staged to the latest version 🚀 (#5751)
* chore(package): update lint-staged to version 9.0.1

* chore(package): update lockfile package-lock.json
2019-07-02 11:46:27 +00:00
dependabot-preview[bot] 8c94c1f238 Bump lint-staged from 8.2.1 to 9.0.0 (#5750)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.2.1 to 9.0.0.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.2.1...v9.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-02 10:50:04 +00:00
greenkeeper[bot] 911d48b420 Update semver to the latest version 🚀 (#5747)
* fix(package): update semver to version 6.2.0

* chore(package): update lockfile package-lock.json
2019-07-01 23:36:21 +00:00
greenkeeper[bot] 28ca19b91c Update husky to the latest version 🚀 (#5746)
* chore(package): update husky to version 3.0.0

* chore(package): update lockfile package-lock.json
2019-07-01 20:46:49 +00:00
dependabot-preview[bot] c379c51cd6 Bump semver from 6.1.2 to 6.1.3 (#5742)
Bumps [semver](https://github.com/npm/node-semver) from 6.1.2 to 6.1.3.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.1.2...v6.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-01 10:48:32 +00:00
dependabot-preview[bot] 5c77d2b757 Bump graphql from 14.4.0 to 14.4.1 (#5741)
Bumps [graphql](https://github.com/graphql/graphql-js) from 14.4.0 to 14.4.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v14.4.0...v14.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-01 10:39:35 +00:00
dependabot-preview[bot] 1c9b77975e Bump husky from 2.6.0 to 2.7.0 (#5739)
Bumps [husky](https://github.com/typicode/husky) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-28 10:34:58 +00:00
greenkeeper[bot] 87592b64a3 Update graphql to the latest version 🚀 (#5736)
* fix(package): update graphql to version 14.4.0

* chore(package): update lockfile package-lock.json
2019-06-26 23:35:50 +00:00
greenkeeper[bot] 55b32f1229 Update husky to the latest version 🚀 (#5734)
* chore(package): update husky to version 2.6.0

* chore(package): update lockfile package-lock.json
2019-06-26 14:50:39 +00:00
greenkeeper[bot] 2a215319ee Update apollo-server to the latest version 🚀 (#5733)
* fix(package): update apollo-server-express to version 2.6.7

* chore(package): update lockfile package-lock.json
2019-06-26 12:24:05 +00:00
Tom Fox 7e17e4be22 add GraphQL in contents list 2019-06-26 11:17:21 +01:00
greenkeeper[bot] 9020997c96 Update apollo-server to the latest version 🚀 (#5728)
* fix(package): update apollo-server-express to version 2.6.6

* chore(package): update lockfile package-lock.json
2019-06-25 23:33:07 +00:00
Diamond Lewis e08f4f8023 Fix: Winston Logger string interpolation (#5729) 2019-06-25 18:01:00 -05:00
Antonio Davi Macedo Coelho de Castro 5bc79cc3db GraphQL support via cli (#5697)
* Including GraphQL options in CLI - now it was auto-generated

* Improving the way that the headers are passed to the playground

* Including README notes about GraphQL

* Improving final text
2019-06-25 14:44:23 -07:00
Diamond Lewis 7ffb3b65e0 Fix: eslint update to 6.0.1 (#5730) 2019-06-25 16:36:42 -05:00
dependabot-preview[bot] 19098a46ac Bump eslint from 5.16.0 to 6.0.1
Bumps [eslint](https://github.com/eslint/eslint) from 5.16.0 to 6.0.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.16.0...v6.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-25 18:06:28 +00:00
greenkeeper[bot] 34a11971af Update flow-bin to the latest version 🚀 (#5727)
* chore(package): update flow-bin to version 0.102.0

* chore(package): update lockfile package-lock.json
2019-06-25 18:04:07 +00:00
greenkeeper[bot] fa6defc172 Update apollo-server to the latest version 🚀 (#5726)
* fix(package): update apollo-server-express to version 2.6.5

* chore(package): update lockfile package-lock.json
2019-06-25 17:28:38 +00:00
Jeff Gu Kang ad7fc48c97 Postgres: Regex support foreign characters (#5598)
* Fix issue #5293

* Fix issue #5293

* add test

* Revert "add test"

This reverts commit 38b32a627a.

* fix conflicts

* use native package
2019-06-24 16:13:34 -05:00
greenkeeper[bot] b178d5ca6a Update husky to the latest version 🚀 (#5723)
* chore(package): update husky to version 2.5.0

* chore(package): update lockfile package-lock.json
2019-06-24 14:17:24 +00:00
greenkeeper[bot] 9903ea2631 Update eslint-plugin-flowtype to the latest version 🚀 (#5722)
* chore(package): update eslint-plugin-flowtype to version 3.11.1

* chore(package): update lockfile package-lock.json
2019-06-24 13:09:08 +00:00
greenkeeper[bot] 2f99ec5289 Update eslint-plugin-flowtype to the latest version 🚀 (#5720)
* chore(package): update eslint-plugin-flowtype to version 3.11.0

* chore(package): update lockfile package-lock.json
2019-06-24 07:50:25 +00:00
greenkeeper[bot] 5b5c71d78c Update semver to the latest version 🚀 (#5719)
* fix(package): update semver to version 6.1.2

* chore(package): update lockfile package-lock.json
2019-06-24 02:11:48 +00:00
greenkeeper[bot] 387b888168 Update eslint-plugin-flowtype to the latest version 🚀 (#5717)
* chore(package): update eslint-plugin-flowtype to version 3.10.5

* chore(package): update lockfile package-lock.json
2019-06-22 06:26:39 +00:00
greenkeeper[bot] ef3dfa8ad1 Update eslint-plugin-flowtype to the latest version 🚀 (#5713)
* chore(package): update eslint-plugin-flowtype to version 3.10.4

* chore(package): update lockfile package-lock.json
2019-06-21 13:51:07 +00:00
dependabot-preview[bot] 7c295a34f3 Bump node-fetch from 2.5.0 to 2.6.0 (#5712)
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/bitinn/node-fetch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.5.0...v2.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-21 10:58:58 +00:00
dependabot-preview[bot] 523bd7f6a4 Bump flow-bin from 0.101.0 to 0.101.1 (#5711)
Bumps [flow-bin](https://github.com/flowtype/flow-bin) from 0.101.0 to 0.101.1.
- [Release notes](https://github.com/flowtype/flow-bin/releases)
- [Commits](https://github.com/flowtype/flow-bin/compare/v0.101.0...v0.101.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-21 10:52:03 +00:00
dependabot-preview[bot] 3c15252cb1 Bump graphql from 14.2.1 to 14.3.1 (#5710)
Bumps [graphql](https://github.com/graphql/graphql-js) from 14.2.1 to 14.3.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v14.2.1...v14.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-21 10:41:42 +00:00
greenkeeper[bot] e2b0829f6e Update apollo-server to the latest version 🚀 (#5709)
* fix(package): update apollo-server-express to version 2.6.4

* chore(package): update lockfile package-lock.json
2019-06-21 00:38:36 +00:00
dependabot-preview[bot] 8f8a4209e3 Bump @apollographql/graphql-playground-html from 1.6.6 to 1.6.20 (#5707)
Bumps [@apollographql/graphql-playground-html](https://github.com/graphcool/graphql-playground) from 1.6.6 to 1.6.20.
- [Release notes](https://github.com/graphcool/graphql-playground/releases)
- [Commits](https://github.com/graphcool/graphql-playground/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 19:16:38 +00:00
Diamond Lewis 6385deeb6e Add AppSecret to Facebook Auth (#5695)
Closes: https://github.com/parse-community/parse-server/issues/5448
2019-06-20 14:15:57 -05:00
Linus Unnebäck 366e12193e Avoid redundant allowCrossDomain calls (#5696) 2019-06-20 12:08:46 -07:00
dependabot-preview[bot] ad26b469cb Bump apollo-utilities from 1.2.1 to 1.3.2 (#5704)
Bumps [apollo-utilities](https://github.com/apollographql/apollo-client) from 1.2.1 to 1.3.2.
- [Release notes](https://github.com/apollographql/apollo-client/releases)
- [Changelog](https://github.com/apollographql/apollo-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-client/compare/apollo-utilities@1.2.1...apollo-utilities@1.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 13:53:44 +00:00
dependabot-preview[bot] 9bfe005524 Bump apollo-link-http from 1.5.14 to 1.5.15 (#5701)
Bumps [apollo-link-http](https://github.com/apollographql/apollo-link) from 1.5.14 to 1.5.15.
- [Release notes](https://github.com/apollographql/apollo-link/releases)
- [Changelog](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-link/compare/apollo-link-http@1.5.14...apollo-link-http@1.5.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 13:29:21 +00:00
dependabot-preview[bot] 8513c39fe3 Bump apollo-link from 1.2.11 to 1.2.12 (#5705)
Bumps [apollo-link](https://github.com/apollographql/apollo-link) from 1.2.11 to 1.2.12.
- [Release notes](https://github.com/apollographql/apollo-link/releases)
- [Changelog](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-link/compare/apollo-link@1.2.11...apollo-link@1.2.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 12:53:14 +00:00
dependabot-preview[bot] 791e26939c Bump apollo-cache-inmemory from 1.5.1 to 1.6.2 (#5703)
Bumps [apollo-cache-inmemory](https://github.com/apollographql/apollo-client) from 1.5.1 to 1.6.2.
- [Release notes](https://github.com/apollographql/apollo-client/releases)
- [Changelog](https://github.com/apollographql/apollo-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-client/compare/apollo-cache-inmemory@1.5.1...apollo-cache-inmemory@1.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 12:23:05 +00:00
dependabot-preview[bot] 4c30037d1e Bump apollo-client from 2.5.1 to 2.6.3 (#5700)
Bumps [apollo-client](https://github.com/apollographql/apollo-client) from 2.5.1 to 2.6.3.
- [Release notes](https://github.com/apollographql/apollo-client/releases)
- [Changelog](https://github.com/apollographql/apollo-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-client/compare/apollo-client@2.5.1...apollo-client@2.6.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 11:54:54 +00:00
dependabot-preview[bot] 9dd4228674 Bump form-data from 2.3.3 to 2.4.0 (#5706)
Bumps [form-data](https://github.com/form-data/form-data) from 2.3.3 to 2.4.0.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Commits](https://github.com/form-data/form-data/commits/v2.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 11:35:00 +00:00
dependabot-preview[bot] 1a3fb8a75f Bump apollo-upload-client from 10.0.0 to 10.0.1
Bumps [apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) from 10.0.0 to 10.0.1.
- [Release notes](https://github.com/jaydenseric/apollo-upload-client/releases)
- [Changelog](https://github.com/jaydenseric/apollo-upload-client/blob/master/changelog.md)
- [Commits](https://github.com/jaydenseric/apollo-upload-client/compare/v10.0.0...v10.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 11:11:57 +00:00
dependabot-preview[bot] 2ce5c6c508 Bump apollo-link-ws from 1.0.17 to 1.0.18 (#5699)
Bumps [apollo-link-ws](https://github.com/apollographql/apollo-link) from 1.0.17 to 1.0.18.
- [Release notes](https://github.com/apollographql/apollo-link/releases)
- [Changelog](https://github.com/apollographql/apollo-link/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apollographql/apollo-link/compare/apollo-link-ws@1.0.17...apollo-link-ws@1.0.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 11:09:41 +00:00
dependabot-preview[bot] 940c2b221c Bump graphql-upload from 8.0.5 to 8.0.7 (#5698)
Bumps [graphql-upload](https://github.com/jaydenseric/graphql-upload) from 8.0.5 to 8.0.7.
- [Release notes](https://github.com/jaydenseric/graphql-upload/releases)
- [Changelog](https://github.com/jaydenseric/graphql-upload/blob/master/changelog.md)
- [Commits](https://github.com/jaydenseric/graphql-upload/compare/v8.0.5...v8.0.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 11:00:23 +00:00
Antonio Davi Macedo Coelho de Castro fe2e95622f GraphQL Support (#5674)
* GraphQL boilerplate

* Create GraphQL schema without using gql

* Introducing loaders

* Generic create mutation

* create mutation is now working for any data type

* Create mutation for each parse class - partial

* Adding more data types to the class

* Get parse class query

* Generic get query

* Generic delete mutation

* Parse class delete mutation

* Parse class find mutation

* Generic update mutation

* Parse class update mutation

* Fixing initialization problems

* Installing node-fetch again

* Basic implementation for Pointer

* Constructor tests

* API tests boilerplate

* _getGraphQLOptions

* applyGraphQL tests

* GraphQL API initial tests

* applyPlayground tests

* createSubscriptions tests

* ParseGrapjQLSchema tests file

* ParseGraphQLSchema tests

* TypeValidationError

* TypeValidationError

* parseStringValue test

* parseIntValue tests

* parseBooleanValue tests

* parseDateValue tests

* parseValue tests

* parseListValues tests

* parseObjectFields tests

* Default types tests

* Get tests

* First permission test at generic Get operation

* Fixing prepare data

* ApolloClient does not work well with different queries runnning in paralell with different headers

* ApolloClient does not work well with different queries runnning in paralell with different headers

* User 3 tests

* User 3 tests

* Get level permission tests

* Get User specific tests

* Get now support keys argument

* Get now supports include argument

* Get now supports read preferences

* Adding tests for read preference enum type

* Find basic test

* Find permissions test

* Find where argument test

* Order, skip and limit tests

* Error handler

* Find now supports count

* Test for FindResult type

* Improving find count

* Find max limit test

* Find now supports keys, include and includeAll

* Find now supports read preferences

* Basic Create test

* Generic create mutation tests

* Basic update test

* UpdateResult object type test

* Update level permissions tests

* Error handler for default mutations

* Delete mutation basic test

* Delete mutation level permission tests

* Test for string

* String test

* Date test

* Pointer test

* Relation tests

* Changing objects mutations location

* Changing objects queries location

* Create file mutation

* Test for file fields

* Test for null values

* Changing parse classes operations location

* Objects mutations refactoring

* Class specific create object mutation now working

* Update class specific mutation now working

* Specific class delete mutation now working

* Get class specific mutation now working

* Find class specific query now working without where and sort

* Find query for custom classes working with where partially

* Almost all data types working for specfic class find where

* Now only missing relation, geopoint, file and ACL

* Additional tests with Parse classes queries and mutations

* Now only missing relation, geopoint, file and ACL

* Files

* Fiels are now working

* Excluding missing order test temporarly

* Refactoring dates

* Refactoring files

* Default types review

* Refeactoring object queries

* Refactoring class scalar type

* Refactoring class types

* Geo queries are now working

* Fixing centerSphere

* Allow sort on class specific queries

* Supporting bytes

* ACL constraint

* Temporarly removing xit tests

* Fixing some tests because of schema cache

* Removing session token from users

* Parse.User queries and mutations

* Remove test using fit

* Fixing include test that was failing because of schema cache

* Fixing count test for postgres. Postgres does not count with where={} (legacy problem). We should solve it later

* Fix null values test for postgres. It is evaluating null as undefined (legacy problem) and we should fix is later.

* Fixing schema change test that was failing because of schema cache

* Add GraphQL File type parseLiteral tests

* Refeactoring users

* Including sign up mutation

* Fix failing test

* Improve default GraphQL types tests coverage

* Including some tests for data types

* Including additional pointer test:

* Fixing some tests

* more data type tests

* Include Bytes and Polygon data types tests

* Polygons test

* Merging other tests

* Fixing some postgres tests
2019-06-19 17:19:47 -07:00
Linus Unnebäck 922251a398 Avoid calling allowCrossDomain twice per request (#5682)
`api.use('/', middleware, ...)` will end up calling `middleware` for _every_ request, even if no routers in the `...` part matches.

This is because passing a router to express is just like passing any other route handler. The only thing that happens when it doesn't match a route is that it calls `next`, but by that point, the middleware has already run. 

The changes in the PR avoids adding the middleware twice for every route except file upload routes. Which will make express not call `allowCrossDomain` twice for every incoming request.
2019-06-19 16:47:26 -07:00
Jack Wearden 559096f1c2 Allow disabling workaround for since-fixed MongoDB bug (#5617)
* Allow disabling workaround for fixed MongoDB bug

* skipMongoDBServer13732Workaround description fix

* flip test boolean

* Remove CLI flag, use databaseVersion & engine

* Revert "Remove CLI flag, use databaseVersion & engine"

This reverts commit 042d1ba19f.

* clean up
2019-06-19 17:30:08 -05:00
Diamond Lewis fcdf2d7947 Sign in with Apple Auth Provider (#5694)
* Sign in with Apple Auth Provider

Closes: https://github.com/parse-community/parse-server/issues/5632

Should work out of the box.

* remove required options
2019-06-19 16:05:09 -05:00
dependabot-preview[bot] 947c6beede Bump ws from 7.0.0 to 7.0.1 (#5693)
Bumps [ws](https://github.com/websockets/ws) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.0.0...7.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-18 13:00:44 +00:00
greenkeeper[bot] 09d0fb4daa Update babel-eslint to the latest version 🚀 (#5690)
* chore(package): update babel-eslint to version 10.0.2

* chore(package): update lockfile package-lock.json
2019-06-17 14:48:24 +00:00
Yao Bin 2a45ee02fb 📝 Document custom pages options (#5687) 2019-06-14 15:35:09 -05:00
Antonio Davi Macedo Coelho de Castro 466a049bd0 Fix ttl flaky test (#5686) 2019-06-13 15:06:16 -05:00
greenkeeper[bot] 1e2037753a Update lint-staged to the latest version 🚀 (#5685)
* chore(package): update lint-staged to version 8.2.1

* chore(package): update lockfile package-lock.json
2019-06-13 19:40:58 +00:00
greenkeeper[bot] 4e48609c49 Update eslint-plugin-flowtype to the latest version 🚀 (#5679)
* chore(package): update eslint-plugin-flowtype to version 3.10.3

* chore(package): update lockfile package-lock.json
2019-06-13 19:30:42 +00:00
Arthur Cinader 5204e5a111 Back merge 3.4.4 into master (#5683)
* 3.4.3 changelog and version bump

* nit
2019-06-13 11:43:29 -07:00
Diamond Lewis 7590ee9799 Fix #5678 (#5681)
* Fix #5678

* Revert "Fix #5678"

This reverts commit 106b6ddd95.

* revert #5627
2019-06-13 13:40:58 -05:00
Antonio Davi Macedo Coelho de Castro 8b667cf048 Fix 5651 (#5684)
* Fix 5651

* Fix User initilization test
2019-06-13 13:40:38 -05:00
greenkeeper[bot] 089b809aed Update eslint-plugin-flowtype to the latest version 🚀 (#5677)
* chore(package): update eslint-plugin-flowtype to version 3.10.2

* chore(package): update lockfile package-lock.json
2019-06-13 08:04:23 +00:00
Diamond Lewis aa68cd84d0 Update Dockerfile (#5676) 2019-06-12 17:39:07 -07:00
Arthur Cinader 72da264c22 Prepare for 3.4.1 (#5673)
* changelog for 3.4.1

* bump package

* Fix layout
2019-06-12 15:57:20 -07:00
Diamond Lewis 8709daf698 Merge pull request from GHSA-2479-qvv7-47qq
* Failing test

* provide fix

* clearer test

* failing expect
2019-06-12 16:12:11 -05:00
Tom Fox 54ba9a0f00 security.md grammar improvements 2019-06-12 18:33:21 +01:00
dependabot-preview[bot] df4ceb5a45 Bump eslint-plugin-flowtype from 3.9.1 to 3.10.1 (#5662)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.9.1 to 3.10.1.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.9.1...v3.10.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-12 06:01:02 +00:00
greenkeeper[bot] af82dd7bdd Update eslint-plugin-flowtype to the latest version 🚀 (#5656)
* chore(package): update eslint-plugin-flowtype to version 3.10.0

* chore(package): update lockfile package-lock.json
2019-06-12 05:41:21 +00:00
dependabot-preview[bot] ea63def0e1 Bump prettier from 1.18.1 to 1.18.2 (#5658)
Bumps [prettier](https://github.com/prettier/prettier) from 1.18.1 to 1.18.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.18.1...1.18.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-12 02:35:55 +00:00
greenkeeper[bot] 2b6ec893d1 Update flow-bin to the latest version 🚀 (#5663)
* chore(package): update flow-bin to version 0.101.0

* chore(package): update lockfile package-lock.json
2019-06-12 02:22:13 +00:00
greenkeeper[bot] cdc45caa80 Update husky to the latest version 🚀 (#5669)
* chore(package): update husky to version 2.4.1

* chore(package): update lockfile package-lock.json
2019-06-12 02:06:57 +00:00
greenkeeper[bot] 1123913ad4 Update supports-color to the latest version 🚀 (#5665)
* chore(package): update supports-color to version 7.0.0

* chore(package): update lockfile package-lock.json
2019-06-12 01:23:39 +00:00
Diamond Lewis cb2b60bd90 Flaky Test (#5670) 2019-06-11 19:54:23 -05:00
Diamond Lewis 5d8b1535ac Flaky Tests (#5668)
Properly cleanup cache and database between tests. This includes indexes.
2019-06-11 16:31:27 -07:00
Diamond Lewis d13bdbcea6 Flaky Tests (#5666)
Closes: https://github.com/parse-community/parse-server/issues/5651

Building on https://github.com/parse-community/parse-server/pull/5551
2019-06-11 14:05:32 -05:00
Diamond Lewis 7a080478b5 Fix #5654 (#5664)
* Fix #5654

* fix tests

* throw error instead
2019-06-11 13:40:34 -05:00
Faysal Ahmed a58653ada8 Support LinkedIn v2 API (#5660) 2019-06-11 11:23:08 -05:00
greenkeeper[bot] d06d020c13 Update prettier to the latest version 🚀 (#5645)
* chore(package): update prettier to version 1.18.1

* chore(package): update lockfile package-lock.json
2019-06-09 00:24:30 +00:00
greenkeeper[bot] b86a07e632 Update mime to the latest version 🚀 (#5646)
* fix(package): update mime to version 2.4.4

* chore(package): update lockfile package-lock.json
2019-06-08 23:20:25 +00:00
Arthur Cinader 38756421d1 try to generate secure key correctly this time. (#5653) 2019-06-08 13:53:12 -07:00
Arthur Cinader c016cf7960 add a gh token to give travis the rights to be me. (#5650) 2019-06-08 08:49:17 -07:00
Arthur Cinader 3341688e18 put deploy secure stuff in right section
add email
2019-06-08 07:57:16 -07:00
Arthur Cinader 476c945d7e Futzing with travis (#5648)
* Attempt to change npm credentials
from folovilmart to acinader

* Attempt to change npm credentials
from folovilmart to acinader
2019-06-08 07:42:37 -07:00
dependabot-preview[bot] 33399b3f11 Bump lint-staged from 8.1.7 to 8.2.0
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.1.7 to 8.2.0.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.1.7...v8.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-07 12:55:21 +00:00
dependabot-preview[bot] 5d06c9a6f7 Bump prettier from 1.17.1 to 1.18.0 (#5644)
Bumps [prettier](https://github.com/prettier/prettier) from 1.17.1 to 1.18.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.17.1...1.18.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-07 12:53:16 +00:00
Diamond Lewis 35524b8df9 Update Dockerfile (#5641) 2019-06-06 18:11:07 -05:00
dependabot-preview[bot] 0846ba148c Bump mongodb from 3.2.6 to 3.2.7 (#5636)
Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 3.2.6 to 3.2.7.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/master/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/compare/v3.2.6...v3.2.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-06 22:07:20 +00:00
greenkeeper[bot] d54e705b7c Update husky to the latest version 🚀 (#5638)
* chore(package): update husky to version 2.4.0

* chore(package): update lockfile package-lock.json
2019-06-05 17:21:40 +00:00
Olivier Allouch 7fc0d45b89 Database version in features (#5627)
* adding database.version in the serverInfo (only MongoDB, it gives undefined when using Postgres)

* . correction of old 'features' tests
. adding engine and database in the StorageAdapter interface and implementations

* . version retrieval done in performInitialization
. PostgreSQL version

* performInitialization now returns a Promise
2019-06-03 16:58:21 -05:00
greenkeeper[bot] 266d6328a3 Update flow-bin to the latest version 🚀 (#5628)
* chore(package): update flow-bin to version 0.100.0

* chore(package): update lockfile package-lock.json
2019-05-31 13:52:04 -05:00
Olivier Allouch e370b9377a Update CONTRIBUTING.md (#5624)
* Update CONTRIBUTING.md

in "npm run test:win" , the ":win" msut be removed
I think it's because of the cross-env

* also removing the :win from coverage:win

* Update CONTRIBUTING.md
2019-05-30 16:19:34 -05:00
Diamond Lewis cc6d474dcb Schema Cache Improvement 2 (#5616)
* schema hasClass improvement

* create object improvement

* destroy object

* update object

* hasClass test rewrite

* more tests

* improve signing up users
2019-05-30 11:14:05 -05:00
greenkeeper[bot] 9f226a254a Update flow-bin to the latest version 🚀 (#5621)
* chore(package): update flow-bin to version 0.99.1

* chore(package): update lockfile package-lock.json
2019-05-29 17:58:10 +00:00
greenkeeper[bot] c5a1a98f03 Update flow-bin to the latest version 🚀 (#5620)
* chore(package): update flow-bin to version 0.99.0

* chore(package): update lockfile package-lock.json
2019-05-29 04:59:53 +00:00
greenkeeper[bot] 94e1bb6ec3 Update semver to the latest version 🚀 (#5619)
* fix(package): update semver to version 6.1.1

* chore(package): update lockfile package-lock.json
2019-05-28 17:41:00 +00:00
greenkeeper[bot] b804245cbe Update express to the latest version 🚀 (#5615)
* fix(package): update express to version 4.17.1

* chore(package): update lockfile package-lock.json
2019-05-26 05:01:48 +00:00
Diamond Lewis f7716f2f87 Schema Cache Improvements (#5612)
* Cache Improvements

* improve tests

* more tests

* clean-up

* test with singlecache

* ensure indexes exists

* remove ALL_KEYS

* Add Insert Test

* enableSingleSchemaCache default true

* Revert "enableSingleSchemaCache default true"

This reverts commit 323e7130fb.

* further optimization

* refactor enforceFieldExists

* coverage improvements

* improve tests

* remove flaky test

* cleanup

* Learned something new
2019-05-24 16:42:27 -05:00
greenkeeper[bot] cae858e16a Update mongodb to the latest version 🚀 (#5613)
* fix(package): update mongodb to version 3.2.6

* chore(package): update lockfile package-lock.json
2019-05-24 00:39:01 +00:00
dependabot-preview[bot] fe61f724d6 Bump eslint-plugin-flowtype from 3.9.0 to 3.9.1 (#5611)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.9.0 to 3.9.1.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.9.0...v3.9.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-23 10:46:27 +00:00
greenkeeper[bot] 091ecfbbf8 Update semver to the latest version 🚀 (#5608)
* fix(package): update semver to version 6.1.0

* chore(package): update lockfile package-lock.json
2019-05-22 21:33:47 +00:00
Arthur Cinader e18b9a5e56 Prepare for 3.4.0 release (#5606)
* Prepare for 3.4.0 release

* Update CHANGELOG.md
2019-05-22 12:46:23 -07:00
dependabot[bot] e9b8752c3f Bump mongodb from 3.2.4 to 3.2.5 (#5604)
* Bump mongodb from 3.2.4 to 3.2.5

Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 3.2.4 to 3.2.5.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/master/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/compare/v3.2.4...v3.2.5)

Signed-off-by: dependabot[bot] <support@dependabot.com>

* Tweak expected error message in a test to match a change
in the underlying package.
see: https://github.com/mongodb-js/mongodb-core/commit/83e224b

cc: @davimacedo
2019-05-21 22:33:31 +00:00
greenkeeper[bot] 46408ee5e8 Update babel7 to the latest version 🚀 (#5605)
* chore(package): update @babel/core to version 7.4.5

* chore(package): update @babel/preset-env to version 7.4.5

* chore(package): update lockfile package-lock.json
2019-05-21 21:14:52 +00:00
greenkeeper[bot] 2c524604df Update express to the latest version 🚀 (#5600)
* fix(package): update express to version 4.17.0

* chore(package): update lockfile package-lock.json
2019-05-17 02:30:48 +00:00
Antonio Davi Macedo Coelho de Castro 2da2a27373 Fixing Redis adapter tests (#5599) 2019-05-16 15:26:45 -07:00
greenkeeper[bot] dc7d3f149f Update eslint-plugin-flowtype to the latest version 🚀 (#5596)
* chore(package): update eslint-plugin-flowtype to version 3.9.0

* chore(package): update lockfile package-lock.json
2019-05-15 19:04:28 +00:00
greenkeeper[bot] 07ff4112d4 Update mime to the latest version 🚀 (#5595)
* fix(package): update mime to version 2.4.3

* chore(package): update lockfile package-lock.json
2019-05-15 14:43:14 +00:00
dependabot[bot] 47a86be7ba Bump husky from 2.2.0 to 2.3.0 (#5594)
Bumps [husky](https://github.com/typicode/husky) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v2.2.0...v2.3.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-15 10:35:49 +00:00
greenkeeper[bot] d84f56bb0e Update lint-staged to the latest version 🚀 (#5593)
* chore(package): update lint-staged to version 8.1.7

* chore(package): update lockfile package-lock.json
2019-05-15 07:14:03 +00:00
Antonio Davi Macedo Coelho de Castro afa74d655d Futzing with read preference (#3963)
* allow setting readpreference when using rest api.

* take out partially complete unit test.

* oops. nit

* Include read preference option for find directly from api and adding few more tests

* Adding catch for all tests

* Keep same check for get and find

* Turn read preference case insensitive

* Includes and subqueries read preferences through API

* Fixing bugs regarding changes that were done in master branch during the last year

* Changing behavior to make includeReadPreference and subqueryReadPreference to follow readPreference by default
2019-05-14 12:58:02 -07:00
Antonio Davi Macedo Coelho de Castro 893f1d376e Remove test delays (#5579)
* Changing __indexBuildCompletionCallbackForTests callback to serverStartComplete

* Improving serverStartComplete callback to avoid production unhandled promise rejection

* Add test to check inexistence of unhandled promise rejection on server fail

* Removing some hooks delays

* Removing delay after reconfigureServer

* Improving code style
2019-05-14 11:34:51 -07:00
dependabot[bot] 2f161c25cd Bump prettier from 1.17.0 to 1.17.1 (#5590)
Bumps [prettier](https://github.com/prettier/prettier) from 1.17.0 to 1.17.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.17.0...1.17.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-14 10:33:23 +00:00
William George 5a8a9472e9 Remove userSensitiveFields default value. (#5588) 2019-05-13 17:14:31 -07:00
greenkeeper[bot] 3bb1796e12 Update jsdoc to the latest version 🚀 (#5586)
* chore(package): update jsdoc to version 3.6.2

* chore(package): update lockfile package-lock.json
2019-05-12 17:15:31 +00:00
greenkeeper[bot] 908270aba1 Update pg-promise to the latest version 🚀 (#5585)
* fix(package): update pg-promise to version 8.7.2

* chore(package): update lockfile package-lock.json
2019-05-12 07:51:55 +00:00
Diamond LewisandTom Fox 63b7146a3c Update README.md (#5583)
* Update README.md

* bump Live Queries

* Apply suggestions from code review

Co-Authored-By: Tom Fox <tomfox@surprises.io>
2019-05-11 19:14:22 -05:00
Diamond Lewis 0ce4eeae72 LiveQuery: Add options for Redis (#5584)
Closes: https://github.com/parse-community/parse-server/issues/5387
2019-05-11 19:13:41 -05:00
Diamond Lewis b9c936f594 Add Docs for Server Config Definitions (#5581)
* Add Docs for Server Config Definitions

* protected field and sort alphabetically

* Add links

* nits
2019-05-11 15:25:31 -05:00
Antonio Davi Macedo Coelho de Castro 90c81c1750 Validates permission before calling beforeSave trigger (#5546)
* Test to reproduce the problem

* Validating update before calling beforeSave trigger

* Fixing lint

* Commenting code

* Improving the code
2019-05-11 10:37:27 -07:00
greenkeeper[bot] 2cc21bf1f2 Update eslint-plugin-flowtype to the latest version 🚀 (#5582)
* chore(package): update eslint-plugin-flowtype to version 3.8.2

* chore(package): update lockfile package-lock.json
2019-05-11 09:06:43 +00:00
Diamond Lewis b4d915bbbb Support direct access server option (#5550)
* Support direct access config

test options

* add test

* fix test

* fix definitions

* improve docs

* Update .travis.yml

* Revert "Update .travis.yml"

This reverts commit 407f138495.
2019-05-10 14:34:19 -05:00
greenkeeper[bot] f15360cb12 Update pg-promise to the latest version 🚀 (#5580)
* fix(package): update pg-promise to version 8.7.1

* chore(package): update lockfile package-lock.json
2019-05-10 19:21:29 +00:00
greenkeeper[bot] 31c9728b3a Update nyc to the latest version 🚀 (#5576)
* chore(package): update nyc to version 14.1.1

* chore(package): update lockfile package-lock.json
2019-05-10 03:07:24 +00:00
Diamond Lewis 4c996baf72 Update Travis CI Cache (#5575)
`prepare` causes `npm ci` to be used. `.npm` is the recommended cache directory. Should speed up build times.

https://docs.npmjs.com/cli/ci
2019-05-09 22:04:40 -05:00
greenkeeper[bot] 8a543bfda4 Update pg-promise to the latest version 🚀 (#5574)
* fix(package): update pg-promise to version 8.7.0

* chore(package): update lockfile package-lock.json
2019-05-09 20:16:46 +00:00
Arthur Cinader 87da62bca2 Logger: Fix timestamp and format (#5571)
* remove no-op config of logger

* add a test to check on the timestamp

* add a test to verify that we
get non json console loggging by default

* configure transports to include
timestamps in files

* Add failing test to confirm that WinstonLoggerAdapter
is not filtering on level.

* actually fix the test to refelect the facth that this isn't the problem

* Remove bogus date ranges that are now failing
becuase we have timestamps.
2019-05-09 14:58:54 -05:00
Antonio Davi Macedo Coelho de Castro 81ecf2fd74 Fix jasmine 3.4 (#5573)
* Fix failing tests

* just ignore the test for now.

* Bumping jasmine

* Fix pg unhandled exception

* Improving the way the test is fixed

* Fix unhandled failed promise in postgres test

* Solving unhandled promise fail on redis test

* Returning the excluded test

* Fixing package-lock

* Fix unhandled promise from redis test
2019-05-09 09:12:30 -07:00
greenkeeper[bot] 9232fcc601 Update mongodb to the latest version 🚀 (#5572)
* fix(package): update mongodb to version 3.2.4

* chore(package): update lockfile package-lock.json
2019-05-09 00:12:47 +00:00
dependabot[bot] dabdf3b8a5 Bump eslint-plugin-flowtype from 3.7.0 to 3.8.1 (#5570)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.7.0 to 3.8.1.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.7.0...v3.8.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-08 10:32:03 +00:00
greenkeeper[bot] cc3f210bf6 Update flow-bin to the latest version 🚀 (#5567)
* chore(package): update flow-bin to version 0.98.1

* chore(package): update lockfile package-lock.json
2019-05-06 17:19:12 +00:00
greenkeeper[bot] f325224ffb Update jsdoc to the latest version 🚀 (#5565)
* chore(package): update jsdoc to version 3.6.1

* chore(package): update lockfile package-lock.json
2019-05-05 19:54:46 +00:00
greenkeeper[bot] 1e6b48a754 Update jsdoc to the latest version 🚀 (#5563)
* chore(package): update jsdoc to version 3.6.0

* chore(package): update lockfile package-lock.json
2019-05-05 00:26:15 +00:00
Diamond Lewis 87b0093873 (chore): Fix broken codecov (#5554) 2019-05-03 18:01:10 -07:00
dependabot[bot] 7fd2a91503 Bump lint-staged from 8.1.5 to 8.1.6 (#5559)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.1.5 to 8.1.6.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.1.5...v8.1.6)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-03 10:52:00 +00:00
dependabot[bot] fdf08c4f09 Bump body-parser from 1.18.3 to 1.19.0 (#5531)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.18.3 to 1.19.0.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.18.3...1.19.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-02 19:47:08 +00:00
greenkeeper[bot] e5f34c3393 Update nyc to the latest version 🚀 (#5555)
* chore(package): update nyc to version 14.1.0

* chore(package): update lockfile package-lock.json
2019-05-02 19:27:37 +00:00
Diamond Lewis e0a25d9030 Fix flaky test. Give em a little more time. (#5557)
Similar to https://github.com/parse-community/parse-server/pull/3453
2019-05-02 13:59:01 -05:00
Diamond Lewis 005fd78275 PG: Fix updating mixed array (#5552)
* PG: Fix updating mixed array

* Revert "PG: Fix updating mixed array"

This reverts commit 5a441413c0.

* simple fix
2019-05-02 12:44:17 -05:00
greenkeeper[bot] d4f4667008 Update eslint-plugin-flowtype to the latest version 🚀 (#5556)
* chore(package): update eslint-plugin-flowtype to version 3.7.0

* chore(package): update lockfile package-lock.json
2019-05-02 16:22:19 +00:00
greenkeeper[bot] 1b0d20d53c Update husky to the latest version 🚀 (#5553)
* chore(package): update husky to version 2.2.0

* chore(package): update lockfile package-lock.json
2019-05-02 04:35:38 +00:00
Antonio Davi Macedo Coelho de Castro 8f0e7f3bcc Fix test that is failing the build (#5551) 2019-05-01 17:29:53 -05:00
Diamond Lewis 9594c4b59f Add useNewUrlParser options to GridFSBucketAdapter (#5548)
* Add useNewUrlParser options to GridFSBucketAdapter

* allow overriding default
2019-05-01 00:44:10 -05:00
Diamond Lewis 057fc40c1d Postgres: Query notEqualTo GeoPoint (#5549)
* Postgres: Query notEqualTo GeoPoint

* remove templated strings
2019-04-30 23:29:44 -05:00
ananfang fa97df5f4e Decode Date JSON value at LiveQuery (#5540) 2019-04-30 13:00:23 -05:00
greenkeeper[bot] 2e2ee06279 Update ws to the latest version 🚀 (#5547)
* fix(package): update ws to version 7.0.0

* chore(package): update lockfile package-lock.json
2019-04-30 16:42:31 +00:00
Tom Fox 99a1b13e23 Improve contributing guide (#5543)
* Update CONTRIBUTING.md

* add note about installing code command
2019-04-29 18:47:49 +01:00
dependabot[bot] 748a9bf52d Bump winston-daily-rotate-file from 3.8.0 to 3.9.0 (#5544)
Bumps [winston-daily-rotate-file](https://github.com/winstonjs/winston-daily-rotate-file) from 3.8.0 to 3.9.0.
- [Release notes](https://github.com/winstonjs/winston-daily-rotate-file/releases)
- [Commits](https://github.com/winstonjs/winston-daily-rotate-file/compare/v3.8.0...v3.9.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-29 10:32:49 +00:00
Tom Fox 022b33e83c Remove video series reference (#5542) 2019-04-27 19:32:07 +01:00
Antonio Davi Macedo Coelho de Castro db994ed473 Aggregate supports group by date fields (#5538)
* it actually supports group by date fields

* Changing the field name again to see Travis logs

* Adding match stage to the test

* Adding test for group by date fields on postgres
2019-04-26 20:33:03 -05:00
Antonio Davi Macedo Coelho de Castro 43c41925a2 Local tests failing for timezones <= -7 (#5536) 2019-04-26 18:26:20 -05:00
greenkeeper[bot] 9c56d5c7e9 Update babel7 to the latest version 🚀 (#5537)
* chore(package): update @babel/cli to version 7.4.4

* chore(package): update @babel/core to version 7.4.4

* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.4.4

* chore(package): update @babel/plugin-transform-flow-strip-types to version 7.4.4

* chore(package): update @babel/preset-env to version 7.4.4

* chore(package): update lockfile package-lock.json
2019-04-26 22:33:11 +00:00
Tom Fox b919c36db0 3.3.0 release notes & version bump (#5534)
* Update CHANGELOG.md

* bump version to 3.3.0
2019-04-26 22:22:36 +01:00
greenkeeper[bot] c70e015751 Update flow-bin to the latest version 🚀 (#5532)
* chore(package): update flow-bin to version 0.98.0

* chore(package): update lockfile package-lock.json
2019-04-26 20:43:13 +00:00
dependabot[bot] 3608b19b3a Bump parse from 2.3.2 to 2.4.0 (#5529)
Bumps [parse](https://github.com/parse-community/Parse-SDK-JS) from 2.3.2 to 2.4.0.
- [Release notes](https://github.com/parse-community/Parse-SDK-JS/releases)
- [Changelog](https://github.com/parse-community/Parse-SDK-JS/blob/master/CHANGELOG.md)
- [Commits](https://github.com/parse-community/Parse-SDK-JS/compare/2.3.2...2.4.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-25 10:42:55 +00:00
Julien Quéré 7122ca05c4 Fix issue on count with Geo constraints and mongo (issue #5285) (#5286)
* Add a tests that fails due to issue #5285

* Make test code much simpler

* Fix #5285 by rewriting query (replacing $nearSphere by $geoWithin)

All credit goes to @dplewis !

* move logic to transform
2019-04-24 20:28:13 -05:00
greenkeeper[bot] 7e130c5421 Update husky to the latest version 🚀 (#5527)
* chore(package): update husky to version 2.1.0

* chore(package): update lockfile package-lock.json
2019-04-24 22:15:51 +00:00
greenkeeper[bot] f7bcfcea24 Update husky to the latest version 🚀 (#5526)
* chore(package): update husky to version 2.0.0

* chore(package): update lockfile package-lock.json
2019-04-24 16:53:18 +00:00
Omair Vaiyani a1e1cef6d2 Add beforeLogin trigger with support for auth providers (#5445)
* Add beforeLogin trigger with support for auth providers

* adjust comment that boxed off beforeLogin to a negative use-case only

* add internal error to help future maintainers regarding use of beforeLogin

* let beforeLogin accept className or constructor like other hook types

* add assertions for beforeLogin trigger className validation
2019-04-23 08:24:20 -07:00
dependabot[bot] 3e003ee9f4 Bump nyc from 13.1.0 to 14.0.0 (#5517)
Bumps [nyc](https://github.com/istanbuljs/nyc) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/istanbuljs/nyc/compare/v13.1.0...v14.0.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-16 10:54:55 +00:00
dependabot[bot] 828ae7e708 Bump mongodb from 3.2.2 to 3.2.3 (#5492)
Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/master/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 12:07:19 +00:00
dependabot[bot] 2998b2a1ca Bump eslint-plugin-flowtype from 3.5.0 to 3.6.1 (#5495)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.5.0 to 3.6.1.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.5.0...v3.6.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 11:51:26 +00:00
dependabot[bot] 1f109de7a9 Bump mime from 2.4.0 to 2.4.2 (#5493)
Bumps [mime](https://github.com/broofa/node-mime) from 2.4.0 to 2.4.2.
- [Release notes](https://github.com/broofa/node-mime/releases)
- [Changelog](https://github.com/broofa/node-mime/blob/master/CHANGELOG.md)
- [Commits](https://github.com/broofa/node-mime/compare/v2.4.0...v2.4.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 11:29:05 +00:00
dependabot[bot] c0b8de7a76 Bump prettier from 1.16.4 to 1.17.0 (#5512)
Bumps [prettier](https://github.com/prettier/prettier) from 1.16.4 to 1.17.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.16.4...1.17.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 11:06:00 +00:00
dependabot[bot] 0bad5f40e4 Bump bcrypt from 3.0.5 to 3.0.6 (#5511)
Bumps [bcrypt](https://github.com/kelektiv/node.bcrypt.js) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/kelektiv/node.bcrypt.js/releases)
- [Changelog](https://github.com/kelektiv/node.bcrypt.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kelektiv/node.bcrypt.js/compare/v3.0.5...v3.0.6)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 10:57:57 +00:00
dependabot[bot] dc9434645b Bump flow-bin from 0.96.0 to 0.97.0 (#5510)
Bumps [flow-bin](https://github.com/flowtype/flow-bin) from 0.96.0 to 0.97.0.
- [Release notes](https://github.com/flowtype/flow-bin/releases)
- [Commits](https://github.com/flowtype/flow-bin/compare/v0.96.0...v0.97.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 10:48:35 +00:00
dependabot[bot] 266db27c72 Bump pg-promise from 8.6.4 to 8.6.5 (#5509)
Bumps [pg-promise](https://github.com/vitaly-t/pg-promise) from 8.6.4 to 8.6.5.
- [Release notes](https://github.com/vitaly-t/pg-promise/releases)
- [Commits](https://github.com/vitaly-t/pg-promise/compare/8.6.4...8.6.5)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-15 10:38:29 +00:00
Sam Ilic 6ffc41345f Winston 3 upgrade (#5496)
* ⚡ Release 3.1.3 (#5267)

* ⚡ Release 3.1.3

* Update CHANGELOG.md

* ⬆️ Bump winston and winston-daily-rotate-file

Bumps [winston](https://github.com/winstonjs/winston) and [winston-daily-rotate-file](https://github.com/winstonjs/winston-daily-rotate-file). These dependencies needed to be updated together.

Updates `winston` from 2.4.4 to 3.1.0
- [Release notes](https://github.com/winstonjs/winston/releases)
- [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md)
- [Commits](https://github.com/winstonjs/winston/compare/2.4.4...3.1.0)

Updates `winston-daily-rotate-file` from 1.7.2 to 3.5.1
- [Release notes](https://github.com/winstonjs/winston-daily-rotate-file/releases)
- [Commits](https://github.com/winstonjs/winston-daily-rotate-file/compare/v1.7.2...v3.5.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>

* Rewrote WinstonLogger to work with winston 3.x api

* Changed winston logger test to use winston-transport

* Added winston-transport dependency

* Close and remove transports before adding them again

* Changed to strict equal

* Override adapter name

* Updated and added getLogs tests

* Bump winston and winston-daily-rotate-file

Bumps [winston](https://github.com/winstonjs/winston) and [winston-daily-rotate-file](https://github.com/winstonjs/winston-daily-rotate-file). These dependencies needed to be updated together.

Updates `winston` from 2.4.4 to 3.2.0
- [Release notes](https://github.com/winstonjs/winston/releases)
- [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md)
- [Commits](https://github.com/winstonjs/winston/compare/2.4.4...3.2.0)

Updates `winston-daily-rotate-file` from 1.7.2 to 3.6.0
- [Release notes](https://github.com/winstonjs/winston-daily-rotate-file/releases)
- [Commits](https://github.com/winstonjs/winston-daily-rotate-file/compare/v1.7.2...v3.6.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>

* Fixed tests, updated parse logging

* Fixed tests, better error logging

* Fix failing tests

* Updates as per review
2019-04-14 18:03:33 -05:00
Arthur Cinader 943134812e Revert "Bump jasmine from 3.1.0 to 3.4.0" (#5506)
This reverts commit e396612254.
2019-04-14 17:40:59 -05:00
Müller Zsolt 019cf0a986 added an RFC 7662 compliant OAuth2 auth adapter (#4910)
* added an RFC 7662 compliant OAuth2 auth adapter

* forgot to add the actual auth adapter to the previous commit

* fixed lint errors

* * added test coverage
* changed option names in auth adapter from snake case to camel case
* added underscore prefix to helper function names
* merged consecutive logger calls into one call and use JSON.stringify() to convert JSON objects to strings
* changed error handling (ParseErrors are no longer thrown, but returned)

* added description of the "debug" option and added this option to the tests too

* added a check of the "debug" option to the unittests and replaced require() of the logger with an import (the former does not work correctly)

* added AuthAdapter based auth adapter runtime validation to src/Adapters/Auth/index.js, added capability to define arbitrary providernames with an "adapter" property in auth config, replaced various "var" keywords with "const" in oauth2.js

* incorporated changes requested by flovilmart (mainly that oauth2 is now not a standalone adapter, but can be selected by setting the "oauth2" property to true in auth config

* modified oauth2 adapter as requested by flovilmart

* bugfix: defaultAdapter can be null in loadAuthAdapter() of index.js (my change broke the tests)

* added TODO on need for a validateAdapter() to validate auth adapters

* test cases and cleanup
2019-04-11 11:05:55 -05:00
Tulsi Sapkota a3746cab00 Cannot change password when maxPasswordHistory is 1 (#5191)
* Negitive to zero and positive to same value

* add failing test
2019-04-10 10:12:09 -07:00
CoderickLamar c7eb7daeae Fix for count being very slow on large Parse Classes' collections (Postgres) (#5330)
* Changed count to be approximate. Should help with postgres slowness

* refactored last commit to only fall back to estimate if no complex query

* handlign variables correctly

* Trying again because it was casting to lowercase table names which doesnt work for us/

* syntax error

* Adding quotations to pg query

* hopefully final pg fix

* Postgres will now use an approximate count unless there is a more complex query specified

* handling edge case

* Fix for count being very slow on large Parse Classes' collections in Postgres. Replicating fix for Mongo in issue 5264

* Fixed silly spelling error resulting from copying over notes

* Lint fixes

* limiting results to 1 on approximation

* suppress test that we can no longer run for postgres

* removed tests from Postgres that no longer apply

* made changes requested by dplewis

* fixed count errors

* updated package.json

* removed test exclude for pg

* removed object types from method

* test disabled for postgres

* returned type

* add estimate count test

* fix mongo test
2019-04-08 17:59:15 -05:00
dependabot[bot] e396612254 Bump jasmine from 3.1.0 to 3.4.0
Bumps [jasmine](https://github.com/jasmine/jasmine-npm) from 3.1.0 to 3.4.0.
- [Release notes](https://github.com/jasmine/jasmine-npm/releases)
- [Commits](https://github.com/jasmine/jasmine-npm/compare/v3.1.0...v3.4.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-04 10:21:49 +00:00
greenkeeper[bot] cf00dfa1c3 Update parse to the latest version 🚀 (#5486)
* fix(package): update parse to version 2.3.2

* chore(package): update lockfile package-lock.json
2019-04-04 03:10:35 +00:00
greenkeeper[bot] b208fbf6a9 Update eslint-plugin-flowtype to the latest version 🚀 (#5480)
* chore(package): update eslint-plugin-flowtype to version 3.5.0

* chore(package): update lockfile package-lock.json
2019-04-03 17:52:30 +00:00
dependabot[bot] d779144da1 Bump @babel/core from 7.4.0 to 7.4.3 (#5478)
Bumps [@babel/core](https://github.com/babel/babel) from 7.4.0 to 7.4.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.0...v7.4.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-03 11:32:53 +00:00
dependabot[bot] 3227b0f4ae Bump @babel/preset-env from 7.4.2 to 7.4.3 (#5477)
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.4.2 to 7.4.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.2...v7.4.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-03 11:02:28 +00:00
dependabot[bot] bfe60049b3 Bump @babel/cli from 7.2.3 to 7.4.3 (#5476)
Bumps [@babel/cli](https://github.com/babel/babel) from 7.2.3 to 7.4.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.2.3...v7.4.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-03 10:52:35 +00:00
dependabot[bot] 5174afd300 Bump @babel/plugin-proposal-object-rest-spread from 7.4.0 to 7.4.3 (#5475)
Bumps [@babel/plugin-proposal-object-rest-spread](https://github.com/babel/babel) from 7.4.0 to 7.4.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.0...v7.4.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-03 10:42:42 +00:00
greenkeeper[bot] 44189802e9 Update commander to the latest version 🚀 (#5473)
* fix(package): update commander to version 2.20.0

* chore(package): update lockfile package-lock.json
2019-04-03 01:06:43 +00:00
Georges Jamous 214aa2e450 using per-key basis queue (#5420)
* adding KeyPromiseQueue

* nit

* removing secondary object and using a tuple

* using array

* nits

* some tests

* Minor refinements

* removing old adapter

* dummy change, travis test not found

* travis test missing, dummy change

* revrting mistake

* reverting mistake

* indentation fix

* additional tests for coverage

* extending coverage

* nits

* fixing mistake

* better code
2019-04-02 10:07:31 -07:00
Diego Vieira c3eb256139 Update README.md (#5431)
Improve getting started for Docker.
2019-04-02 09:55:06 -07:00
Tom Fox dd1c0fe2e8 Add twitter follow badge (#5464)
* Add twitter follow button

* fix twitter link
2019-04-02 13:06:45 +01:00
greenkeeper[bot] f137f4dd96 Update flow-bin to the latest version 🚀 (#5469)
* chore(package): update flow-bin to version 0.96.0

* chore(package): update lockfile package-lock.json
2019-04-02 03:44:41 +00:00
dependabot[bot] 8df75a730a Bump eslint from 5.15.3 to 5.16.0 (#5468)
Bumps [eslint](https://github.com/eslint/eslint) from 5.15.3 to 5.16.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.15.3...v5.16.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-01 10:47:18 +00:00
Arthur Cinader a3c8629fd9 Release notes and version bump for 3.2.3 (#5467) 2019-03-30 18:32:37 -07:00
Diamond Lewis 730f5c5c3d Allow test credentials for Facebook Auth (#5466)
* Allow test credentials for Facebook Auth

* node_env testing
2019-03-30 18:21:41 -05:00
Arthur Cinader edf5b513dc Protected fields fix (#5463)
* fix minor spelling mistake

* Always process userSensitiveFields if they exist

* Cover change to protectedFields
Add start of some more tests for protectedFields
which i need to do to document the feature.

* re-arrange promise deck chairs to not
swallow errors.

* remove noop code

* protect agains the case where options.protectedFields
is set without a _User permission.
2019-03-30 15:38:52 -07:00
Arthur Cinader 11976b8d24 Changelog and package increment for 3.2.1 (#5461)
* changelog and package increment for 3.2.1

* update package-lock to match package.json
2019-03-29 21:27:57 -07:00
Arthur Cinader 03cd314495 Changelog for forthcoming 3.2 release (#5449)
* Changelog for forthcoming 3.2 release

* fix change log comparison

* add parse sdk upgrade to notes
make links

* fix parse sdk version
2019-03-29 17:27:40 -07:00
Arthur Cinader 9d064890d7 Remove an invisible that is causing display issue (#5458)
* Remove an invisible that is causing display issue

* move code transfer to bottom
2019-03-30 00:07:39 +00:00
greenkeeper[bot] 7bf868e751 Update parse to the latest version 🚀 (#5457)
* fix(package): update parse to version 2.3.1

* chore(package): update lockfile package-lock.json
2019-03-29 21:32:13 +00:00
greenkeeper[bot] b2ec5c10ce Update ws to the latest version 🚀 (#5453)
* fix(package): update ws to version 6.2.1

* chore(package): update lockfile package-lock.json
2019-03-27 13:00:32 +00:00
dependabot[bot] 4e07d16148 Bump semver from 5.6.0 to 6.0.0 (#5451)
Bumps [semver](https://github.com/npm/node-semver) from 5.6.0 to 6.0.0.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.6.0...v6.0.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-27 10:48:29 +00:00
dependabot[bot] f2c332ea6a Bump mongodb from 3.2.1 to 3.2.2 (#5446)
Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/master/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/compare/v3.2.1...v3.2.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-25 11:01:31 +00:00
Tom Fox d64a23b2f7 Remove flovilmart's email from the COC (#5438)
* remove flovilmart email

* change email
2019-03-22 12:54:02 -07:00
greenkeeper[bot] 3c39e3232f Update mongodb to the latest version 🚀 (#5441)
* fix(package): update mongodb to version 3.2.1

* chore(package): update lockfile package-lock.json
2019-03-21 20:31:10 +00:00
dependabot[bot] 6376aea320 Bump @babel/preset-env from 7.4.1 to 7.4.2 (#5439)
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.4.1 to 7.4.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.1...v7.4.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-21 10:34:08 +00:00
greenkeeper[bot] a27371b6a4 Update babel7 to the latest version 🚀 (#5436)
* chore(package): update @babel/preset-env to version 7.4.1

* chore(package): update lockfile package-lock.json
2019-03-20 02:29:54 +00:00
greenkeeper[bot] 9f2415420b Update babel7 to the latest version 🚀 (#5435)
* chore(package): update @babel/core to version 7.4.0

* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.4.0

* chore(package): update @babel/plugin-transform-flow-strip-types to version 7.4.0

* chore(package): update @babel/preset-env to version 7.4.0

* chore(package): update lockfile package-lock.json
2019-03-19 22:40:56 +00:00
greenkeeper[bot] 7eddc4c658 Update bcrypt to the latest version 🚀 (#5433)
* chore(package): update bcrypt to version 3.0.5

* chore(package): update lockfile package-lock.json
2019-03-19 13:16:30 +00:00
dependabot[bot] 4cd12e9049 Bump eslint from 5.15.2 to 5.15.3 (#5432)
Bumps [eslint](https://github.com/eslint/eslint) from 5.15.2 to 5.15.3.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.15.2...v5.15.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-19 10:30:37 +00:00
dependabot[bot] adf0633e7b Bump eslint from 5.15.1 to 5.15.2 (#5428)
Bumps [eslint](https://github.com/eslint/eslint) from 5.15.1 to 5.15.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.15.1...v5.15.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-18 10:41:57 +00:00
dependabot[bot] 4d9083bdfa Bump flow-bin from 0.94.0 to 0.95.1 (#5427)
Bumps [flow-bin](https://github.com/flowtype/flow-bin) from 0.94.0 to 0.95.1.
- [Release notes](https://github.com/flowtype/flow-bin/releases)
- [Commits](https://github.com/flowtype/flow-bin/compare/v0.94.0...v0.95.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-18 10:32:27 +00:00
greenkeeper[bot] a75f129542 Update pg-promise to the latest version 🚀 (#5424)
* fix(package): update pg-promise to version 8.6.4

* chore(package): update lockfile package-lock.json
2019-03-16 07:05:22 +00:00
Diamond Lewis 2c51011018 Fix Discourse Badge (#5423) 2019-03-15 17:41:35 -07:00
moonion d84566ac28 Ajax password reset (#5332)
* adapted public api route for use with ajax

* Elegant error handling

* Fixed error return

* Public API error flow redone, tests

* Fixed code to pre-build form

* Public API change password return params

* Reverted errors in resetPassword

* Fixed querystring call

* Success test on ajax password reset

* Added few more routes to tests for coverage

* More tests and redone error return slightly

* Updated error text

* Console logs removal, renamed test, added {} to if

* Wrong error sent

* Revert changes

* Revert "Revert changes"

This reverts commit 68ee2c44bf.

* real revert of {}

* nits and test fix

* fix tests

* throw proper error
2019-03-14 16:06:18 -05:00
Guido Ruiz bf033becbd Properly handle return values in beforeSave (#5228)
* added failing test case to CloudCode.spec.js

a possible bug found where beforeSave does not apply changes to request
object if the beforeSave hook ends with 'true' returned

* moddified triggers to return null when beforeSave
also changed test cases to be more descriptive + added extra test case that returns promise in the beforeSave

* address original issue

* Revert "address original issue"

This reverts commit e01c57d1de.

* fix promises and tests

* Add a test to verify that a failed beforeChange hook will
prevent updating the object.
2019-03-14 13:17:29 -05:00
Diamond Lewis 8622e5c378 Prevent Stalebot from closing PRs (#5416) 2019-03-11 19:24:11 -05:00
FatBat bcda96be51 Fixes #5354. (#5396) 2019-03-11 17:13:07 -07:00
greenkeeper[bot] 01e8e5d827 Update pg-promise to the latest version 🚀 (#5415)
* fix(package): update pg-promise to version 8.6.3

* chore(package): update lockfile package-lock.json
2019-03-07 19:05:16 +00:00
greenkeeper[bot] 8d2514571c Update ws to the latest version 🚀 (#5414)
* fix(package): update ws to version 6.2.0

* chore(package): update lockfile package-lock.json
2019-03-06 08:22:18 +00:00
greenkeeper[bot] 3b96ccb03c Update pg-promise to the latest version 🚀 (#5413)
* fix(package): update pg-promise to version 8.6.2

* chore(package): update lockfile package-lock.json
2019-03-05 15:34:06 +00:00
greenkeeper[bot] a452488d61 Update eslint to the latest version 🚀 (#5412)
* chore(package): update eslint to version 5.15.1

* chore(package): update lockfile package-lock.json
2019-03-05 05:40:39 +00:00
greenkeeper[bot] 378f8d28e1 Update pg-promise to the latest version 🚀 (#5410)
* fix(package): update pg-promise to version 8.6.1

* chore(package): update lockfile package-lock.json
2019-03-04 21:02:48 +00:00
greenkeeper[bot] f798a60475 Update pg-promise to the latest version 🚀 (#5408)
* fix(package): update pg-promise to version 8.6.0

* chore(package): update lockfile package-lock.json
2019-03-02 22:23:21 +00:00
greenkeeper[bot] 92eb2656e0 Update eslint to the latest version 🚀 (#5407)
* chore(package): update eslint to version 5.15.0

* chore(package): update lockfile package-lock.json
2019-03-02 07:13:30 +00:00
greenkeeper[bot] 2266976296 Update lint-staged to the latest version 🚀 (#5406)
* chore(package): update lint-staged to version 8.1.5

* chore(package): update lockfile package-lock.json
2019-03-01 17:17:32 +00:00
dependabot[bot] ad506280b2 Bump jasmine-spec-reporter from 4.1.0 to 4.2.1 (#5405)
Bumps [jasmine-spec-reporter](https://github.com/bcaudan/jasmine-spec-reporter) from 4.1.0 to 4.2.1.
- [Release notes](https://github.com/bcaudan/jasmine-spec-reporter/releases)
- [Changelog](https://github.com/bcaudan/jasmine-spec-reporter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bcaudan/jasmine-spec-reporter/compare/v4.1.0...v4.2.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-01 10:56:16 +00:00
dependabot[bot] 1aecd18a72 Bump uuid from 3.1.0 to 3.3.2 (#5404)
Bumps [uuid](https://github.com/kelektiv/node-uuid) from 3.1.0 to 3.3.2.
- [Release notes](https://github.com/kelektiv/node-uuid/releases)
- [Changelog](https://github.com/kelektiv/node-uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kelektiv/node-uuid/compare/v3.1.0...v3.3.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-03-01 10:46:26 +00:00
greenkeeper[bot] 56b0786379 Update flow-bin to the latest version 🚀 (#5402)
* chore(package): update flow-bin to version 0.94.0

* chore(package): update lockfile package-lock.json
2019-03-01 03:33:59 +00:00
pateldharad 6eaefd95ae Password requirement custom message (#5399)
* Added validationError(custom message) for Password requirement fail

* Changed validationError from valodationError in README.md
2019-02-28 13:17:31 -08:00
Diamond Lewis 1e7cc7ba00 Revert uws to 10.148.1 (#5398)
https://www.reddit.com/r/node/comments/91kgte/uws_has_been_deprecated/

The release 100.0.1 was a blank project again.

I've pinned all the packages.

This package has been moved to https://github.com/uNetworking/uWebSockets.js

We should consider migrating to it.
2019-02-28 14:12:33 -06:00
dependabot[bot] e0db6eb2ea Bump eslint from 5.14.0 to 5.14.1 (#5377)
Bumps [eslint](https://github.com/eslint/eslint) from 5.14.0 to 5.14.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.14.0...v5.14.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-25 22:35:41 +00:00
greenkeeper[bot] 8d284cd967 Update babel7 to the latest version 🚀 (#5395)
* chore(package): update @babel/core to version 7.3.4

* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.3.4

* chore(package): update @babel/plugin-transform-flow-strip-types to version 7.3.4

* chore(package): update @babel/preset-env to version 7.3.4

* chore(package): update lockfile package-lock.json
2019-02-25 22:13:30 +00:00
awgeorge 86e9351cd0 Reverse update. 2019-02-23 07:28:39 -08:00
awgeorge c5a5f57451 Updates based on review 2019-02-23 07:28:39 -08:00
awgeorge ff33c9939c Use ES6 code 2019-02-23 07:28:39 -08:00
awgeorge 1420df7d7a Update based on @milesrichardson comment https://github.com/parse-community/parse-server/pull/5334#discussion_r252693409 2019-02-23 07:28:39 -08:00
awgeorge ede262c109 Fix linter errors 2019-02-23 07:28:39 -08:00
awgeorge 0dec4931a0 Add filter sensitive fields logic that apply CLPs\nAdd protectedFields CLP\nAdd defaults for protectedFields CLP\nFix tests 2019-02-23 07:28:39 -08:00
awgeorge b343de0c70 Set default protectedFields and remove previous filter logic 2019-02-23 07:28:39 -08:00
awgeorge 95831a5b22 Add new definition and update tests to reflect 2019-02-23 07:28:39 -08:00
awgeorge 493fc99bcb Fix tests to catch errors 2019-02-23 07:28:39 -08:00
awgeorge c69130e0e0 Added negative scenarios for #5301.
- Public read ACL should never expose PII to authenticated and non-authenticated
    - Explicit ACL like custom user Role should be able to read PII
2019-02-23 07:28:39 -08:00
awgeorge 7a6dc3ff29 Add tests to prove a user with valid read ACLs still can't read PII data. 2019-02-23 07:28:39 -08:00
greenkeeper[bot] b246c4ed90 Update parse to the latest version 🚀 (#5385)
* fix(package): update parse to version 2.2.1

* chore(package): update lockfile package-lock.json
2019-02-22 19:00:30 +00:00
dependabot[bot] 9710c755c5 Bump uws from 10.148.1 to 100.0.1 (#5382)
Bumps [uws](https://github.com/uNetworking/uWebSockets.js) from 10.148.1 to 100.0.1.
- [Release notes](https://github.com/uNetworking/uWebSockets.js/releases)
- [Commits](https://github.com/uNetworking/uWebSockets.js/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-21 10:36:19 +00:00
greenkeeper[bot] 7d0d4cea33 Update pg-promise to the latest version 🚀 (#5381)
* fix(package): update pg-promise to version 8.5.6

* chore(package): update lockfile package-lock.json
2019-02-20 23:20:31 +00:00
dependabot[bot] 95870f4ecc Bump eslint-plugin-flowtype from 3.4.1 to 3.4.2 (#5375)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.4.1...v3.4.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-18 10:55:38 +00:00
dependabot[bot] 852cd6a179 Bump @babel/core from 7.2.2 to 7.3.3 (#5374)
Bumps [@babel/core](https://github.com/babel/babel) from 7.2.2 to 7.3.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.2.2...v7.3.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-18 10:45:40 +00:00
dependabot[bot] 2a2130f85a Bump eslint from 5.13.0 to 5.14.0 (#5373)
Bumps [eslint](https://github.com/eslint/eslint) from 5.13.0 to 5.14.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.13.0...v5.14.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-18 10:36:03 +00:00
greenkeeper[bot] 416c190993 Update ws to the latest version 🚀 (#5369)
* fix(package): update ws to version 6.1.4

* chore(package): update lockfile package-lock.json
2019-02-16 17:37:09 +00:00
dependabot[bot] 8bfccf1d4e Bump nyc from 13.2.0 to 13.3.0 (#5367)
Bumps [nyc](https://github.com/istanbuljs/nyc) from 13.2.0 to 13.3.0.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/istanbuljs/nyc/compare/v13.2.0...v13.3.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-15 10:43:04 +00:00
dependabot[bot] 99943ff52b Bump lint-staged from 8.1.3 to 8.1.4 (#5366)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.1.3 to 8.1.4.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.1.3...v8.1.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-15 10:33:23 +00:00
greenkeeper[bot] bc05d71824 Update deepcopy to the latest version 🚀 (#5364)
* fix(package): update deepcopy to version 2.0.0

* chore(package): update lockfile package-lock.json
2019-02-14 17:37:10 +00:00
dependabot[bot] dc76a3ad41 [Security] Bump handlebars from 4.0.12 to 4.1.0 (#5362)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.12 to 4.1.0. **This update includes security fixes.**
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/v4.1.0/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.1.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-14 04:59:23 +00:00
greenkeeper[bot] 226234e6bc Update parse to the latest version 🚀 (#5361)
* fix(package): update parse to version 2.2.0

* chore(package): update lockfile package-lock.json
2019-02-14 03:03:32 +00:00
greenkeeper[bot] 2be5a784e4 Update flow-bin to the latest version 🚀 (#5360)
* chore(package): update flow-bin to version 0.93.0

* chore(package): update lockfile package-lock.json
2019-02-14 00:18:21 +00:00
greenkeeper[bot] 7bb94c8560 Update follow-redirects to the latest version 🚀 (#5359)
* fix(package): update follow-redirects to version 1.7.0

* chore(package): update lockfile package-lock.json
2019-02-13 23:47:11 +00:00
dependabot[bot] 61d8516eb3 Bump eslint-plugin-flowtype from 3.2.1 to 3.4.1 (#5356)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.2.1 to 3.4.1.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.2.1...v3.4.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-13 10:34:35 +00:00
greenkeeper[bot] 4d07f0274a Update bcrypt to the latest version 🚀 (#5351)
* chore(package): update bcrypt to version 3.0.4

* chore(package): update lockfile package-lock.json
2019-02-06 23:22:21 +00:00
dependabot[bot] db0ac480af Bump nyc from 13.1.0 to 13.2.0 (#5346)
Bumps [nyc](https://github.com/istanbuljs/nyc) from 13.1.0 to 13.2.0.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/istanbuljs/nyc/compare/v13.1.0...v13.2.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-05 10:38:54 +00:00
greenkeeper[bot] 5b21a056d8 Update flow-bin to the latest version 🚀 (#5345)
* chore(package): update flow-bin to version 0.92.1

* chore(package): update lockfile package-lock.json
2019-02-05 06:42:54 +00:00
greenkeeper[bot] 8d60726a15 Update babel7 to the latest version 🚀 (#5344)
* chore(package): update @babel/plugin-proposal-object-rest-spread to version 7.3.2

* chore(package): update lockfile package-lock.json
2019-02-05 01:11:33 +00:00
dependabot[bot] 400ff4a750 Bump lint-staged from 8.1.1 to 8.1.3 (#5343)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.1.1 to 8.1.3.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.1.1...v8.1.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-04 11:01:22 +00:00
dependabot[bot] e9f8c616dd Bump prettier from 1.16.3 to 1.16.4 (#5342)
Bumps [prettier](https://github.com/prettier/prettier) from 1.16.3 to 1.16.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.16.3...1.16.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-04 10:36:59 +00:00
dependabot[bot] 44b26e1993 Bump eslint from 5.12.1 to 5.13.0 (#5341)
Bumps [eslint](https://github.com/eslint/eslint) from 5.12.1 to 5.13.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.12.1...v5.13.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-04 10:27:28 +00:00
dependabot[bot] 1973dee59b Bump flow-bin from 0.91.0 to 0.92.0 (#5339)
Bumps [flow-bin](https://github.com/flowtype/flow-bin) from 0.91.0 to 0.92.0.
- [Release notes](https://github.com/flowtype/flow-bin/releases)
- [Commits](https://github.com/flowtype/flow-bin/compare/v0.91.0...v0.92.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-01 09:17:26 -05:00
dependabot[bot] 5471b6a083 Bump deepcopy from 1.0.0 to 1.0.1 (#5340)
Bumps [deepcopy](https://github.com/sasaplus1/deepcopy.js) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/sasaplus1/deepcopy.js/releases)
- [Changelog](https://github.com/sasaplus1/deepcopy.js/blob/master/HISTORY.md)
- [Commits](https://github.com/sasaplus1/deepcopy.js/compare/1.0.0...1.0.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-01 10:50:14 +00:00
Steve Clay 038d7bd727 postgres: Refuse to build unsafe JSON lists for contains in Postgres (#5337) 2019-01-31 12:23:40 -05:00
dependabot[bot] 7e48b7a1f1 Bump prettier from 1.16.2 to 1.16.3 (#5335)
Bumps [prettier](https://github.com/prettier/prettier) from 1.16.2 to 1.16.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.16.2...1.16.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-31 10:33:46 +00:00
dependabot[bot] 78eb3df235 Bump prettier from 1.16.1 to 1.16.2 (#5331)
Bumps [prettier](https://github.com/prettier/prettier) from 1.16.1 to 1.16.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.16.1...1.16.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-30 10:34:22 +00:00
dependabot[bot] bdb497c9a3 Bump lint-staged from 8.1.0 to 8.1.1 (#5329)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.1.0 to 8.1.1.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.1.0...v8.1.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-29 10:41:22 +00:00
dependabot[bot] 22c39ed6c8 Bump @babel/preset-env from 7.2.3 to 7.3.1
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.2.3 to 7.3.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.2.3...v7.3.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-28 00:43:32 +00:00
dependabot[bot] 06550e3830 Bump eslint from 5.12.0 to 5.12.1
Bumps [eslint](https://github.com/eslint/eslint) from 5.12.0 to 5.12.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.12.0...v5.12.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-28 00:34:47 +00:00
Florent Vilmart bfec804ee1 chore: bump mongodb to 3.1.13 (#5325) 2019-01-27 19:13:11 -05:00
Darren Black 55d367ec03 Check the correct verbose field when hiding push details (#5324) 2019-01-27 15:35:33 -05:00
Darren Black 6a93806c62 Hides token contents in logStartupOptions if they arrive as a buffer (#5322)
* Hides token contents in logStartupOptions if they arrive as a buffer

* Hides all push details in logStartupOptions unless we're in verbose mode
2019-01-27 13:59:15 -05:00
Marc Smith 1cbe8bd394 Docs Typo (#5317) 2019-01-24 22:21:58 -05:00
Marc Smith 03604b1bc6 Docs Typo #2 (#5318)
Plus added example.com url instead of '....'
2019-01-24 22:20:27 -05:00
dependabot[bot] bb47caad56 Bump ws from 6.1.2 to 6.1.3 (#5316)
Bumps [ws](https://github.com/websockets/ws) from 6.1.2 to 6.1.3.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/6.1.2...6.1.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-24 10:36:22 +00:00
dependabot[bot] f84f245903 Bump prettier from 1.16.0 to 1.16.1 (#5315)
Bumps [prettier](https://github.com/prettier/prettier) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.16.0...1.16.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-23 10:36:36 +00:00
dependabot[bot] 098053da35 Bump prettier from 1.15.3 to 1.16.0
Bumps [prettier](https://github.com/prettier/prettier) from 1.15.3 to 1.16.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.15.3...1.16.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-22 18:08:56 +00:00
dependabot[bot] 355f9f6b52 Bump @babel/plugin-proposal-object-rest-spread from 7.2.0 to 7.3.1 (#5312)
Bumps [@babel/plugin-proposal-object-rest-spread](https://github.com/babel/babel) from 7.2.0 to 7.3.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.2.0...v7.3.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-22 13:07:29 -05:00
dependabot[bot] 3851641b5a Bump flow-bin from 0.83.0 to 0.91.0 (#5302)
* Bump flow-bin from 0.83.0 to 0.91.0

Bumps [flow-bin](https://github.com/flowtype/flow-bin) from 0.83.0 to 0.91.0.
- [Release notes](https://github.com/flowtype/flow-bin/releases)
- [Commits](https://github.com/flowtype/flow-bin/compare/v0.83.0...v0.91.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>

* fix flow issue
2019-01-19 13:08:42 -05:00
dependabot[bot] 30aa1ded1d Bump eslint-plugin-flowtype from 3.2.0 to 3.2.1 (#5284)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.2.0...v3.2.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-19 12:22:53 -05:00
dependabot[bot] be251811eb Bump eslint from 5.11.1 to 5.12.0 (#5282)
Bumps [eslint](https://github.com/eslint/eslint) from 5.11.1 to 5.12.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.11.1...v5.12.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-19 12:22:42 -05:00
dependabot[bot] 68913d9a20 Bump supports-color from 6.0.0 to 6.1.0 (#5291)
Bumps [supports-color](https://github.com/chalk/supports-color) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/chalk/supports-color/releases)
- [Commits](https://github.com/chalk/supports-color/compare/v6.0.0...v6.1.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-19 12:22:23 -05:00
greenkeeper[bot] 6525b39e83 Update mongodb to the latest version 🚀 (#5300)
* fix(package): update mongodb to version 3.2.0

* chore(package): update lockfile package-lock.json
2019-01-16 22:55:46 +00:00
greenkeeper[bot] 3ebdb63268 Update pg-promise to the latest version 🚀 (#5299)
* fix(package): update pg-promise to version 8.5.5

* chore(package): update lockfile package-lock.json
2019-01-16 13:24:38 +00:00
greenkeeper[bot] b0b3cfa39a Update mongodb to the latest version 🚀 (#5298)
* fix(package): update mongodb to version 3.1.11

* chore(package): update lockfile package-lock.json
2019-01-15 22:16:58 +00:00
Florent Vilmart 32de338247 Discourse (#5296)
* Update ISSUE_TEMPLATE.md

* Update ---report-an-issue.md

* Update ---parse-server-3-0-0.md

* Update ---push-notifications.md

* Update README.md

* Create ---getting-help.md
2019-01-15 12:20:57 -05:00
Florent Vilmart 46ac7e7f11 Adds fix for issue affecting update with CLP (#5269)
* Adds fix for issue affecting update with CLP

* Disable single instance
2019-01-04 14:23:27 -05:00
Perceval Archimbaud 9f2fc88f0f Fix issue #5274 on RestQuery.each and relations (#5276)
* Add test on RestQuery.each with relation

* Fix the failing test for RestQuery.each and relations

* Add test for getRolesForUser

* Fix format for comment

* Remove extra fit
2019-01-04 14:22:46 -05:00
greenkeeper[bot] eb877545de Update follow-redirects to the latest version 🚀 (#5277)
* fix(package): update follow-redirects to version 1.6.1

* chore(package): update lockfile package-lock.json
2019-01-03 10:47:19 +00:00
Florent Vilmart cc0d76966c Update lockfile (#5270) 2019-01-01 13:43:50 -05:00
Florent Vilmart 2d7b9923ef ⚡ Release 3.1.3 (#5267)
* ⚡ Release 3.1.3

* Update CHANGELOG.md
2018-12-30 09:45:19 -05:00
dependabot[bot] d11e93c36c Bump eslint from 5.10.0 to 5.11.1 (#5261)
Bumps [eslint](https://github.com/eslint/eslint) from 5.10.0 to 5.11.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.10.0...v5.11.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-29 12:35:04 -05:00
dependabot[bot] d04a7b5a77 Bump husky from 1.2.1 to 1.3.1 (#5263)
Bumps [husky](https://github.com/typicode/husky) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v1.2.1...v1.3.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-29 12:34:51 -05:00
Diamond Lewis ce7ff2ca44 Add original object to LiveQuery Events (#5265)
* Add original object to LiveQuery Events

* change response original
2018-12-28 17:56:08 -05:00
Benjamin Simonsson de92ce5c49 Fix for count being very slow on large Parse Classes' collections (#5264)
* * Added fix for MongoCollection's count function, so that it uses the much more effecient estimatedDocumentCount if no queries were specified

* * Added missing options when running estimatedDocumentCount for Mongo Collections

* * Fixed issue with checking for zero query for Mongo Collections count
2018-12-28 09:45:36 -05:00
Diamond Lewis 66f594342e Update CHANGELOG.md 2018-12-27 09:06:37 -06:00
greenkeeper[bot] 3a6505a32d Update follow-redirects to the latest version 🚀 (#5260)
* fix(package): update follow-redirects to version 1.6.0

* chore(package): update lockfile package-lock.json
2018-12-25 22:46:16 +00:00
greenkeeper[bot] acf205cd7f Update bcrypt to the latest version 🚀 (#5258)
* chore(package): update bcrypt to version 3.0.3

* chore(package): update lockfile package-lock.json
2018-12-24 04:16:59 +00:00
Diamond Lewis 99698928af Fix for #5207 (#5257) 2018-12-23 19:40:59 -06:00
Florent Vilmart a9aff79cc6 docker: slim docker image to 154Mb (#5248)
* Better dockerfile

* nits

* proper VOLUMES
2018-12-22 11:33:41 -05:00
greenkeeper[bot] df109c0023 Update supports-color to the latest version 🚀 (#5256)
* chore(package): update supports-color to version 6.0.0

* chore(package): update lockfile package-lock.json
2018-12-21 16:02:18 +00:00
greenkeeper[bot] 1d8bcb22bd Update babel7 to the latest version 🚀 (#5255)
* chore(package): update @babel/cli to version 7.2.3

* chore(package): update @babel/plugin-transform-flow-strip-types to version 7.2.3

* chore(package): update @babel/preset-env to version 7.2.3

* chore(package): update lockfile package-lock.json
2018-12-20 13:41:28 +00:00
Diamond Lewis ab32dbc9a2 PG: Fix containedIn query on empty array (#5254)
* PG: Fix containedIn query on empty array

* improve logic
2018-12-19 17:57:55 -06:00
Diamond Lewis 631b1684e2 PG: Updating mixed array test (#5252)
* PG: Updating mixed array test

Currently we can save a mixed array but not update

* build array instead of casting

* fix test

* add recursion
2018-12-18 17:38:05 -06:00
Diamond Lewis 7b3da8b744 PG: Fix updating numeric array (#5251)
* PG: Fix updating numeric array

* lint
2018-12-18 12:11:24 -06:00
Florent Vilmart c762ee4b43 run tests against mongodb 4.0 (#5250)
* run tests against mongodb 4.0

* Adds compatibility shields
2018-12-18 11:41:18 -05:00
Florent Vilmart 315d3bfee8 Use current LTS 10.14.2 for dev and ci (#5249) 2018-12-18 11:21:33 -05:00
dependabot[bot] 980c2cda3a ⬆️ Bump eslint from 5.9.0 to 5.10.0 (#5247)
Bumps [eslint](https://github.com/eslint/eslint) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.9.0...v5.10.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-17 07:32:44 -05:00
dependabot[bot] 3ea632fa1b Bump winston from 2.4.1 to 2.4.4 (#5203)
Bumps [winston](https://github.com/winstonjs/winston) from 2.4.1 to 2.4.4.
- [Release notes](https://github.com/winstonjs/winston/releases)
- [Changelog](https://github.com/winstonjs/winston/blob/2.4.4/CHANGELOG.md)
- [Commits](https://github.com/winstonjs/winston/compare/2.4.1...2.4.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-15 08:07:44 -05:00
dependabot[bot] f08ca7b9cc Bump follow-redirects from 1.5.9 to 1.5.10 (#5204)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.5.9 to 1.5.10.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.5.9...v1.5.10)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-15 08:07:26 -05:00
greenkeeper[bot] 4b8dc9409f Update babel7 to the latest version 🚀 (#5243)
* chore(package): update @babel/core to version 7.2.2

* chore(package): update @babel/plugin-transform-flow-strip-types to version 7.2.0

* chore(package): update @babel/preset-env to version 7.2.0

* chore(package): update lockfile package-lock.json
2018-12-15 11:42:07 +00:00
Diamond Lewis d478e001b5 PG: Support multiple global config (#5242)
* PG: Support Multiple Configs

* rename test

* refactor
2018-12-14 17:39:07 -06:00
Diamond Lewis 8c419ec52a Update CONTRIBUTING.md (#5241) 2018-12-14 10:46:37 -05:00
dependabot[bot] 9470dfc88a Bump @babel/plugin-proposal-object-rest-spread from 7.0.0 to 7.2.0 (#5221)
Bumps [@babel/plugin-proposal-object-rest-spread](https://github.com/babel/babel) from 7.0.0 to 7.2.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.0.0...v7.2.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:46:10 -05:00
dependabot[bot] 13626290c0 Bump express from 4.16.2 to 4.16.4 (#5202)
Bumps [express](https://github.com/expressjs/express) from 4.16.2 to 4.16.4.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.16.2...4.16.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:44:27 -05:00
dependabot[bot] cbd31d513e Bump nyc from 12.0.2 to 13.1.0 (#5206)
Bumps [nyc](https://github.com/istanbuljs/nyc) from 12.0.2 to 13.1.0.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/istanbuljs/nyc/compare/v12.0.2...v13.1.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:44:11 -05:00
dependabot[bot] 3e414a8c60 Bump lint-staged from 7.3.0 to 8.1.0 (#5211)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 7.3.0 to 8.1.0.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v7.3.0...v8.1.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:43:55 -05:00
dependabot[bot] 38482cf0fe Bump lru-cache from 5.0.0 to 5.1.1 (#5213)
Bumps [lru-cache](https://github.com/isaacs/node-lru-cache) from 5.0.0 to 5.1.1.
- [Release notes](https://github.com/isaacs/node-lru-cache/releases)
- [Commits](https://github.com/isaacs/node-lru-cache/compare/v5.0.0...v5.1.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:43:34 -05:00
dependabot[bot] 8c044ab85b Bump @babel/cli from 7.1.5 to 7.2.0 (#5222)
Bumps [@babel/cli](https://github.com/babel/babel) from 7.1.5 to 7.2.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.1.5...v7.2.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:43:16 -05:00
dependabot[bot] 208f92160d Bump husky from 1.0.0-rc.15 to 1.2.1 (#5237)
Bumps [husky](https://github.com/typicode/husky) from 1.0.0-rc.15 to 1.2.1.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v1.0.0-rc.15...v1.2.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-14 10:42:07 -05:00
greenkeeper[bot] e28770e744 Update pg-promise to the latest version 🚀 (#5234)
* fix(package): update pg-promise to version 8.5.4

* chore(package): update lockfile package-lock.json
2018-12-14 01:29:47 +00:00
greenkeeper[bot] a4b592a355 Update pg-promise to the latest version 🚀 (#5217)
* fix(package): update pg-promise to version 8.5.3

* chore(package): update lockfile package-lock.json
2018-11-30 04:54:59 +00:00
greenkeeper[bot] f0626508d3 Update prettier to the latest version 🚀 (#5216)
* chore(package): update prettier to version 1.15.3

* chore(package): update lockfile package-lock.json
2018-11-30 03:28:53 +00:00
dependabot[bot] bc12b1496a Bump babel-eslint from 10.0.0 to 10.0.1 (#5200)
Bumps [babel-eslint](https://github.com/babel/babel-eslint) from 10.0.0 to 10.0.1.
- [Release notes](https://github.com/babel/babel-eslint/releases)
- [Commits](https://github.com/babel/babel-eslint/compare/v10.0.0...v10.0.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-27 21:01:40 -05:00
dependabot[bot] 80a9b70253 Bump eslint-plugin-flowtype from 3.0.0 to 3.2.0 (#5197)
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 3.0.0 to 3.2.0.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v3.0.0...v3.2.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-27 20:58:37 -05:00
dependabot[bot] 5d9d7601a9 Bump eslint from 5.6.0 to 5.9.0 (#5199)
Bumps [eslint](https://github.com/eslint/eslint) from 5.6.0 to 5.9.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.6.0...v5.9.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-27 20:58:21 -05:00
Florent Vilmart 021a12f648 ⚡ Release 3.1.2 (#5193)
- Removes dev script
- Removes nodemon and problematic dependenceis
- addressed event-stream security debacle
2018-11-26 20:57:18 -05:00
greenkeeper[bot] 6cb00d513b Update mime to the latest version 🚀 (#5194)
* fix(package): update mime to version 2.4.0

* chore(package): update lockfile package-lock.json
2018-11-27 01:39:08 +00:00
greenkeeper[bot] 7984bc2ae3 Update lru-cache to the latest version 🚀 (#5189)
* fix(package): update lru-cache to version 5.0.0

* chore(package): update lockfile package-lock.json
2018-11-21 02:17:19 +00:00
greenkeeper[bot] 6659b11962 Update lru-cache to the latest version 🚀 (#5188)
* fix(package): update lru-cache to version 4.1.4

* chore(package): update lockfile package-lock.json
2018-11-21 00:34:02 +00:00
greenkeeper[bot] 284d04a211 Update ws to the latest version 🚀 (#5180)
* fix(package): update ws to version 6.1.2

* chore(package): update lockfile package-lock.json
2018-11-17 20:29:42 +00:00
greenkeeper[bot] e1de2763c3 Update ws to the latest version 🚀 (#5179)
* fix(package): update ws to version 6.1.1

* chore(package): update lockfile package-lock.json
2018-11-17 07:53:16 +00:00
greenkeeper[bot] e04fd9e7fe Update mongodb to the latest version 🚀 (#5178)
* fix(package): update mongodb to version 3.1.10

* chore(package): update lockfile package-lock.json
2018-11-16 22:16:28 +00:00
greenkeeper[bot] 463e52b063 Update babel7 to the latest version 🚀 (#5173)
* chore(package): update @babel/core to version 7.1.6

* chore(package): update @babel/plugin-transform-flow-strip-types to version 7.1.6

* chore(package): update @babel/preset-env to version 7.1.6

* chore(package): update lockfile package-lock.json
2018-11-14 16:41:59 +00:00
greenkeeper[bot] 29bf0132f9 Update prettier to the latest version 🚀 (#5169)
* chore(package): update prettier to version 1.15.2

* chore(package): update lockfile package-lock.json
2018-11-10 18:12:58 +00:00
greenkeeper[bot] ec06b70b8a Update pg-promise to the latest version 🚀 (#5166)
* fix(package): update pg-promise to version 8.5.2

* chore(package): update lockfile package-lock.json
2018-11-08 06:28:19 +00:00
greenkeeper[bot] 42f946dbc6 Update prettier to the latest version 🚀 (#5165)
* chore(package): update prettier to version 1.15.1

* chore(package): update lockfile package-lock.json
2018-11-07 04:57:29 +00:00
greenkeeper[bot] 3254e425e2 Update prettier to the latest version 🚀 (#5164)
* chore(package): update prettier to version 1.15.0

* chore(package): update lockfile package-lock.json
2018-11-07 03:45:48 +00:00
greenkeeper[bot] 18d9811eaa Update babel7 to the latest version 🚀 (#5163)
* chore(package): update @babel/cli to version 7.1.5

* chore(package): update @babel/core to version 7.1.5

* chore(package): update @babel/preset-env to version 7.1.5

* chore(package): update lockfile package-lock.json
2018-11-07 00:45:14 +00:00
greenkeeper[bot] 216579bb01 Update mongodb to the latest version 🚀 (#5162)
* fix(package): update mongodb to version 3.1.9

* chore(package): update lockfile package-lock.json
2018-11-06 13:49:03 +00:00
Antoine Cormouls d356ef5633 Release 3.1.1 🚀 (#5149)
* Release 3.1.1

* Requested Changes
2018-10-27 11:34:20 -04:00
Diamond Lewis daab3781a2 Support Distinct for special fields (#5144)
* Support Distinct for special fields

* update changelog
2018-10-26 10:21:42 -05:00
Georges Jamous 961abda4eb "Object not found." instead of "Insufficient auth." when using master key (#5133)
* add additional isMaster check

* adding some tests

* nits

* covering all basis
2018-10-22 19:33:43 -04:00
Antoine Cormouls de79b70cbc Ensure all roles are properly loaded #5131 (#5132)
* Fix Limitation Role #5131

Allow to manage Live Query with User that have more than 100 Parse.Roles

* Clean Up

* Add Custom Config Support and Test

* Fix Auth Test

* Switch to Async Function

* Fix restWhere

* Fix Test

* Clean Final Commit

* Lint Fix

* Need to Fix Test Callback

* Fixes broken test

* Restore find() method in spy

* adds restquery-each

* small nit

* adds changelog
2018-10-20 16:45:23 -04:00
greenkeeper[bot] aa9580e59c Update pg-promise to the latest version 🚀 (#5134)
* fix(package): update pg-promise to version 8.5.1

* chore(package): update lockfile package-lock.json
2018-10-19 19:24:35 +00:00
Arthur Cinader c692f075d1 ⚡ Release 3.1.0 (#5127) 2018-10-18 08:19:07 -04:00
Florent Vilmart 4b7037ac9a Fix intense CPU usage when sessionToken is invalid in liveQuery (#5126)
* Ensure we bail out early when auth or userId are not provided (sessionToken fetch is invalid)

* Adds changelog

* better handling of session token errors and client tokens
2018-10-18 07:21:31 -04:00
greenkeeper[bot] 318a784e20 Update bcrypt to the latest version 🚀 (#5128)
* chore(package): update bcrypt to version 3.0.2

* chore(package): update lockfile package-lock.json
2018-10-18 03:21:09 +00:00
Florent Vilmart 7c81290252 Live query CLP (#4387)
* Auth module refactoring in order to be reusable

* Ensure cache controller is properly forwarded from helpers

* Nits

* Adds support for static validation

* Adds support for CLP in Live query (no support for roles yet)

* Adds e2e test to validate liveQuery hooks is properly called

* Adds tests over LiveQueryController to ensure data is correctly transmitted

* nits

* Fixes for flow types

* Removes usage of Parse.Promise

* Use the Auth module for authentication and caches

* Cleaner implementation of getting auth

* Adds authCache that stores auth promises

* Proper testing of the caching

* nits
2018-10-17 17:53:49 -04:00
Arthur Cinader 17bd5c3adb Add changelog for two already merged pr's 2018-10-16 13:19:20 -07:00
greenkeeper[bot] 07220b351c Update eslint-plugin-flowtype to the latest version 🚀 (#5121)
* chore(package): update eslint-plugin-flowtype to version 3.0.0

* chore(package): update lockfile package-lock.json
2018-10-13 23:17:09 +00:00
greenkeeper[bot] 0685a0e7fb Update flow-bin to the latest version 🚀 (#5118)
* chore(package): update flow-bin to version 0.83.0

* chore(package): update lockfile package-lock.json
2018-10-11 18:00:17 +00:00
Arthur Cinader 5fba636517 fix typo 2018-10-11 09:09:56 -07:00
Arthur Cinader f9e108c4ee nit 2018-10-11 09:09:56 -07:00
Arthur Cinader a0de2bcb6b Move logic out of User and Classes controllers
into RestWriter
2018-10-11 09:09:56 -07:00
Arthur Cinader b3b4461fe2 pr comments:
consolidate write operations
also tweak test text
2018-10-11 09:09:56 -07:00
Arthur Cinader 6ebce1832b Expire password reset tokens if user's email changes. 2018-10-11 09:09:56 -07:00
Arthur Cinader 152ff41cf8 fix the expectation on the failing test. 2018-10-11 09:09:56 -07:00
Arthur Cinader 34b51f7289 Add failing test to show
that changing a user's email
does not delete their perishable token.
2018-10-11 09:09:56 -07:00
Arthur Cinader 317682d7f6 Typos 2018-10-11 09:09:56 -07:00
greenkeeper[bot] 8dff708613 Update semver to the latest version 🚀 (#5117)
* fix(package): update semver to version 5.6.0

* chore(package): update lockfile package-lock.json
2018-10-11 00:14:26 +00:00
greenkeeper[bot] 635f54bd08 Update mongodb to the latest version 🚀 (#5115)
* fix(package): update mongodb to version 3.1.8

* chore(package): update lockfile package-lock.json
2018-10-10 22:49:52 +00:00
greenkeeper[bot] 2ce3c9c378 Update mongodb to the latest version 🚀 (#5112)
* fix(package): update mongodb to version 3.1.7

* chore(package): update lockfile package-lock.json
2018-10-09 17:55:58 -04:00
greenkeeper[bot] 645ddaf661 Update commander to the latest version 🚀 (#5108)
* fix(package): update commander to version 2.19.0

* chore(package): update lockfile package-lock.json
2018-10-09 17:55:46 -04:00
greenkeeper[bot] 5373cb7998 Update follow-redirects to the latest version 🚀 (#5111)
* fix(package): update follow-redirects to version 1.5.9

* chore(package): update lockfile package-lock.json
2018-10-09 16:24:35 +00:00
greenkeeper[bot] 2f0e5819f7 Update pg-promise to the latest version 🚀 (#5110)
* fix(package): update pg-promise to version 8.5.0

* chore(package): update lockfile package-lock.json
2018-10-09 13:49:39 +00:00
Florent Vilmart f1bc55bf89 Reduces number of calls to injectDefaultSchema (#5107) 2018-10-08 14:16:29 -04:00
Arthur Cinader 7fe4030453 Return success on sendPasswordResetEmail even if email not found. 2018-10-05 10:03:41 -07:00
greenkeeper[bot] 7a01fa0a67 Update ws to the latest version 🚀 (#5101)
* fix(package): update ws to version 6.1.0

* chore(package): update lockfile package-lock.json
2018-10-05 07:37:43 +00:00
Paulo Reis 937bdf9058 Fix bugs in dev commands and organize the dev script (#5099) 2018-10-03 07:33:53 -04:00
greenkeeper[bot] 22fe390be6 Update flow-bin to the latest version 🚀 (#5095)
* chore(package): update flow-bin to version 0.82.0

* chore(package): update lockfile package-lock.json
2018-10-01 21:39:55 +00:00
Florent Vilmart 135a993d32 Greenkeeper/mongodb runner 4.3.2 (#5094)
* chore(package): update mongodb-runner to version 4.3.2

Closes #5087

* chore(package): update lockfile package-lock.json
2018-09-30 20:00:51 -04:00
greenkeeper[bot] 827e008497 Update babel7 to the latest version 🚀 (#5090)
* chore(package): update @babel/cli to version 7.1.2

* chore(package): update @babel/core to version 7.1.2

* chore(package): update lockfile package-lock.json
2018-09-28 23:44:55 +00:00
greenkeeper[bot] 9f56b537fb Update babel7 to the latest version 🚀 (#5089)
* chore(package): update @babel/cli to version 7.1.1

* chore(package): update @babel/core to version 7.1.1

* chore(package): update lockfile package-lock.json
2018-09-28 21:12:52 +00:00
Ben Briggs 45ff23bde2 postgres: Ensure that setting a value to undefined is a no-op on writes. (#5065) (#5069) 2018-09-26 16:42:06 -04:00
greenkeeper[bot] 4b5fb84678 Update mongodb-runner to the latest version 🚀 (#5086)
* chore(package): update mongodb-runner to version 4.2.0

* chore(package): update lockfile package-lock.json
2018-09-26 20:37:10 +00:00
greenkeeper[bot] 1c024c1ba2 Update babel-eslint to the latest version 🚀 (#5082)
* chore(package): update babel-eslint to version 10.0.0

* chore(package): update lockfile package-lock.json
2018-09-25 23:23:24 +00:00
greenkeeper[bot] db90157191 Update @parse/push-adapter to the latest version 🚀 (#5078)
* fix(package): update @parse/push-adapter to version 3.0.0

* chore(package): update lockfile package-lock.json
2018-09-25 02:56:10 +00:00
Florent Vilmart 045d941aef Remove request and request-promise from dev dependencies (#5077)
* removes from emailverificationtoken spec

* updates winston

* Updates ValidationAndPasswordsReset

* Use local request in schemas

* Removes request in rest.spec

* Removes request from PushRouter0

* removes request from public API

* removes request from index.spec

* Removes request form parse.push spec

* removes request from ParseInstallation spec

* Removes from ParseHooks

* removes request from ParseGlobalConfig.spec

* Removes request from ParseAPI.spec.js

* removes request from LogsRouter

* removes in features

* Filters undefined headers instead of crashing

* Removes request from ParseUser spec

* Removes usage of request in ParseFile.spec.js

* Removes request from AuthAdapters.js

* removes request-promise from ParseGeoPoint.spec

* Removes request-promise from ParseQuery spec

* remove request-promise from UserPII

* removes request-promise from EnableExpressErrorHandler

* Updates RevocableSessionUpgrade spec

* Update RestQuery

* Removes read preferenceOptionM

* ensure we forward auth from URL

* use request in CloudCode.spec.js

* Removes request-promise from JobSchedule.spec

* Removes rp from VerifyUserPassword.spec.js

* Removes rp from PasswordPolicy spec

* Removes rp from ParsePolygon spec

* Removes rp from fullTextSearch spec

* Removes rp from PArseQuery.Aggregate

* Ensure we properly forward errors

* Removes request and request-promise
2018-09-24 17:07:51 -04:00
Florent Vilmart 93a0017b25 remove runtime dependency on request (#5076) 2018-09-23 12:31:08 -04:00
greenkeeper[bot] 4dc4a3a56d Update bcrypt to the latest version 🚀 (#5070)
* chore(package): update bcrypt to version 3.0.1

* chore(package): update lockfile package-lock.json
2018-09-21 13:17:00 +00:00
Stephan Hoogland 979af97b01 Fixed index link to upgrading to parse 3.0.0 (#5060) 2018-09-20 18:07:10 -04:00
greenkeeper[bot] f928ad702f Update prettier to the latest version 🚀 (#5066)
* chore(package): update prettier to version 1.14.3

* chore(package): update lockfile package-lock.json
2018-09-19 15:28:31 +00:00
Eric 91f121eb62 Allow throw native Error in beforeSave hook (#5064) 2018-09-19 07:02:43 -04:00
Florent Vilmart 5177d5d0af Update and rename .stale.yml to stale.yml 2018-09-17 20:29:31 -04:00
greenkeeper[bot] 2613c353d1 Update babel7 to the latest version 🚀 (#5061)
* chore(package): update @babel/cli to version 7.1.0

* chore(package): update @babel/core to version 7.1.0

* chore(package): update @babel/preset-env to version 7.1.0

* chore(package): update lockfile package-lock.json
2018-09-18 00:23:09 +00:00
greenkeeper[bot] 1a6e19ae94 Update mongodb to the latest version 🚀 (#5056)
* fix(package): update mongodb to version 3.1.6

* chore(package): update lockfile package-lock.json
2018-09-15 02:24:12 +00:00
greenkeeper[bot] 0b37af5e13 Update flow-bin to the latest version 🚀 (#5055)
* chore(package): update flow-bin to version 0.81.0

* chore(package): update lockfile package-lock.json
2018-09-14 23:40:19 +00:00
greenkeeper[bot] 037db69f6a Update mongodb to the latest version 🚀 (#5054)
* fix(package): update mongodb to version 3.1.5

* chore(package): update lockfile package-lock.json
2018-09-14 20:11:32 +00:00
greenkeeper[bot] d18d9c5964 Update lodash to the latest version 🚀 (#5050)
* fix(package): update lodash to version 4.17.11

* chore(package): update lockfile package-lock.json
2018-09-12 21:04:56 +00:00
greenkeeper[bot] 826d65770a Update babel7 to the latest version 🚀 (#5049)
* chore(package): update @babel/core to version 7.0.1

* chore(package): update lockfile
2018-09-12 03:14:00 +00:00
Florent Vilmart 576779b69b Updates JS SDK to 2.1.0 (#5043) 2018-09-08 20:05:32 -04:00
greenkeeper[bot] a3d6d2498c Update flow-bin to the latest version 🚀 (#5021)
* chore(package): update flow-bin to version 0.80.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-09-08 19:34:35 -04:00
greenkeeper[bot] ec2993918c Update commander to the latest version 🚀 (#5035)
* fix(package): update commander to version 2.18.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-09-08 23:14:08 +00:00
Florent Vilmart cfa22d0a62 Use spies for CloudCode logger tests (#5042) 2018-09-08 18:46:26 -04:00
Florent Vilmart 617e8405ff Updates definition build script for babel 7.0 (#5024)
* Updates definition build script for babel 7.0

* run eslint on staged files
2018-09-08 14:12:23 -04:00
llino fefecfeb9a Update 3.0.0.md (#5038)
In example, the parameter is called request but in the function it was calling req
2018-09-08 08:20:34 -04:00
Deokhyun Ko 462f5eb54b fix unexpected 'delete' trigger issue on LiveQuery (#5031)
* fix unexpected 'delete' trigger on LiveQuery when response is an error in cloud function beforeDelete

* tested

* Rebuild CI

* Retest due to local machine testing is passed

* Travis testing failed randomly on Cloud Code Logger. I will retry

* retry

* retry
2018-09-06 07:15:36 -04:00
Florent Vilmart a42101531a Silences warnings from mongodb client (#5025)
* Silences warnings from mongodb client

* Update count, delete and finds to recommended implementations

* With new parser, readPref will be null by default

* Update flaky specs wih async/await style

* Adds gridstore adapter spec

* Use GridFSBucketStorage adapter
2018-09-04 16:15:09 -04:00
Florent Vilmart d83a0b6808 Use Prettier JS (#5017)
* Adds prettier

* Run lint before tests
2018-09-01 13:58:06 -04:00
greenkeeper[bot] 189cd259ee Update jsdoc-babel to the latest version 🚀 (#5022)
* chore(package): update jsdoc-babel to version 0.5.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile

* Use babel 7.0

* Proper configuration for jsdoc
2018-09-01 17:02:31 +00:00
orette c7357ed109 Use the correct function when validating google auth tokens (#5018)
* Use the correct function when validating google auth tokens

httpsRequest.request expects the param postData and has no default value
or validation to check if it is missing before using it. As a result, an
error `TypeError: First argument must be a string or Buffer` is
thrown when an attempt is made to authenticate with Google.

A quick check on the LinkedIn, FB, and twitter authentication adapters
shows they are using httpsRequest.get for their validation calls.

* Correct google auth adapter tests
2018-08-30 20:17:46 -04:00
greenkeeper[bot] 8c0a4430e0 Update nodemon to the latest version 🚀 (#5012)
* chore(package): update nodemon to version 1.18.4

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-29 18:32:39 +00:00
greenkeeper[bot] 36eddbafad Update mongodb-runner to the latest version 🚀 (#5010)
* chore(package): update mongodb-runner to version 4.1.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-29 01:11:37 +00:00
greenkeeper[bot] bd2342ebfa Update babel-eslint to the latest version 🚀 (#5009)
* chore(package): update babel-eslint to version 9.0.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-28 03:22:17 +00:00
greenkeeper[bot] 5236039b1e Update parse to the latest version 🚀 (#5008)
* fix(package): update parse to version 2.0.2

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-27 15:51:56 +00:00
greenkeeper[bot] b51f508d59 Update mongodb to the latest version 🚀 (#5004)
* fix(package): update mongodb to version 3.1.4

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-25 16:58:45 +00:00
John Carlson 61720181b5 Update Gomix to Glitch (#4999) 2018-08-21 13:28:49 +00:00
Florent Vilmart 786ea115d7 Update ---push-notifications.md (#4994)
* Update ---push-notifications.md

* Update ---push-notifications.md
2018-08-19 10:43:02 -04:00
Florent Vilmart 40d2cab159 Update issue templates (#4990)
* Update issue templates

* Update ---report-an-issue.md
2018-08-18 17:15:01 -04:00
Florent Vilmart 07164f55d3 Ensure the docs are properly generated (#4983) 2018-08-18 20:09:13 +00:00
greenkeeper[bot] da63feed99 Update pg-promise to the latest version 🚀 (#4989)
* fix(package): update pg-promise to version 8.4.6

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-18 16:49:48 +00:00
greenkeeper[bot] ddb0fd81c7 Merged by Peril
* fix(package): update semver to version 5.5.1

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-18 04:07:58 +00:00
greenkeeper[bot] 1b1928fd64 Merged by Peril
* chore(package): update flow-bin to version 0.79.1

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-18 03:39:10 +00:00
eddyLazar 41acaa0570 fixed vkontakte validateAuthData (#4977)
* fixed vkontakte validateAuthData

* updated changelog
2018-08-17 15:38:07 -04:00
Florent Vilmart f771160147 ⚡ Release 3.0.0 (#4982) 2018-08-17 13:03:02 -04:00
Florent Vilmart e880941cef Update ISSUE_TEMPLATE.md (#4981) 2018-08-17 12:08:36 -04:00
Florent Vilmart 16607cb12a Update .stale.yml 2018-08-17 11:51:44 -04:00
Florent Vilmart d6cd3da61d Create .stale.yml 2018-08-17 11:26:48 -04:00
greenkeeper[bot] bf1f6024d6 Update flow-bin to the latest version 🚀 (#4978)
* chore(package): update flow-bin to version 0.79.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-17 11:24:51 -04:00
Florent Vilmart 9deaca3c07 Delete Bug_report.md 2018-08-17 11:21:52 -04:00
greenkeeper[bot] d25d1f65dc Update deep-diff to the latest version 🚀 (#4974)
* chore(package): update deep-diff to version 1.0.2

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-16 11:26:02 -04:00
Florent Vilmart b740db335b Allows undefined values to pass from mongo to database controler (#4973)
* Allows undefined values to pass from mongo to database controler

* Adds changelog
2018-08-16 11:24:33 -04:00
Florent Vilmart 942b9b5b1a Adds documentations for adapters and main constructors (#4951)
* wip

* Adds documentations for adapters, and constructors

* nits

* nit
2018-08-15 10:47:21 -04:00
Florent Vilmart 0571675fcd version 3.0.0 API Docs (#4943)
* Adds documentation and docs generation upon merge

* nits
2018-08-15 10:47:21 -04:00
Florent Vilmart 457d51a972 Adds context object in Cloud Code hooks (#4939)
* wip

* Refactors triggers a bit

- Adds testing for hooks and context

* comment nit

* nits
2018-08-15 10:47:21 -04:00
Florent Vilmart 488b2ff231 Migrate to new cloud code interfaces
removes job status object, moves messasge method on req object

Adds 3.0.0 migration guide

Fixes nits about 3.0.0 documentation

Adds update guide to README
2018-08-15 10:47:21 -04:00
Arthur Cinader e46830a57f sync package-lock to package. (#4970) 2018-08-15 10:28:33 -04:00
Florent Vilmart bf9fdb41ed Ensure the ACL is always part of the payload when using select (#4967)
* Ensure the ACL is always part of the payload when using select

* Update changelog

* fix for PG
2018-08-15 09:48:32 -04:00
Arthur Cinader af45b4df74 add log for mailgun adapter update 2018-08-15 09:07:10 -04:00
Arthur Cinader 74218cc36a nit 2018-08-15 09:07:10 -04:00
greenkeeper[bot] 48168feb55 Update request to the latest version 🚀 (#4949)
* fix(package): update request to version 2.88.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-15 09:06:36 -04:00
Florent Vilmart 5e15cc9d4c * fix(package): update mongodb to version 3.1.3 (#4969)
* fix(package): update mongodb to version 3.1.3

Closes #4962

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-15 09:05:57 -04:00
Arthur Cinader 2765f3390d use updated mailgun adapter 2018-08-14 15:33:48 -04:00
Diamond Lewis 4802b1caec Add pipeline key to Aggregate (#4959)
* Add pipeline key to Aggregate

* clean up

* unit tests
2018-08-12 20:05:08 -05:00
Florent Vilmart 9e36a3cc85 ⚡ Release 2.8.4 2018-08-12 19:06:16 -04:00
Florent Vilmart 8ba8a3393d Removes second log when responding with a regular error 2018-08-12 19:04:48 -04:00
Florent Vilmart b9673da07b Refactor all auth adapters to reduce duplications (#4954)
* Refactor all auth adapters to reduce duplications

* Adds mocking and proper testing for all auth adapters

* Proper testing of the google auth adapter

* noit
2018-08-12 11:05:28 -04:00
Diamond Lewis f1b008388c Support include('*') Query (#4947) 2018-08-10 12:51:32 -05:00
greenkeeper[bot] 35da17b32a Update parse to the latest version 🚀 (#4942)
* fix(package): update parse to version 2.0.1

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-09 16:20:28 -04:00
Florent Vilmart 2ae603574c Auth module refactoring in order to be reusable (#4940)
* Auth module refactoring in order to be reusable

* Ensure cache controller is properly forwarded from helpers

* Nits
2018-08-09 13:02:06 -04:00
Florent Vilmart 5d91c1057f Updates many dependencies (#4938) 2018-08-07 12:57:06 -04:00
Florent Vilmart 1aaead319e fix(package): update request to version 2.87.0 (#4935)
Closes #4634
2018-08-07 11:31:18 -04:00
Florent Vilmart 444f67d219 Greenkeeper/commander 2.17.1 (#4936)
* fix(package): update commander to version 2.17.1

Closes #4927

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-08-07 11:31:00 -04:00
Florent Vilmart a021fef830 Adds exposed headers to avoid issue in JS SDK (#4934)
* Adds exposed headers to avoid issue in JS SDK

* Adds test for headers
2018-08-07 11:13:15 -04:00
Florent Vilmart ff25ae254d Update parse SDK to 2.0.0 (#4925)
* WIP: Integrate JS SDK v2

- Removes backbone style callbacks
- Use Promise instead of Parse.Promise

* Fixes ParseObject and ParseRelation

* Updates Parse.Query with promises

* Alls tests should pass

* Ensure a fresh user is used for each test

* Use REST implementation to avoid side effects for username/email duplicates

* Uses js sdk v2
2018-08-05 13:58:07 -04:00
GabrielLomba a61ef7ee2f Fix typo in FilesController.spec (#4917)
* Add the addFileNameHash option that allows users to remove the hash from file names

* Change option name to preserveFileName

* Revert changes to package-lock.json

* Fix typo in FileController.spec
2018-07-27 17:41:04 -04:00
Florent Vilmart dd84c64d16 Pins uws due to drame (#4916) 2018-07-27 09:04:28 -04:00
GabrielLomba 6a151ee135 Add the addFileNameHash option that allows users to remove the hash f… (#4915)
* Add the addFileNameHash option that allows users to remove the hash from file names

* Change option name to preserveFileName

* Revert changes to package-lock.json
2018-07-27 09:04:06 -04:00
greenkeeper[bot] 0363728b83 Update ws to the latest version 🚀 (#4909)
* fix(package): update ws to version 6.0.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-27 08:19:02 -04:00
Florent Vilmart a2c40367fa Ensures the test suites are properly formed for vscode jasmine plugin (#4901)
* Ensures the test suites are properly formed for vscode jasmine plugin

* nit
2018-07-19 07:05:27 +00:00
Florent Vilmart efb6c63a95 Ensure legacy users with authData are not locked out (#4898)
* Adds fix for issue, ensuring legacy users with no ACL are properly handled

* Runs tests only on mongo
2018-07-18 14:42:50 +00:00
Saulo Tauil b22947d4ec Enable express error handler (#4697)
* Propagate error to express handler in all situations

* Call the default error handler if `enableExpressErrorHandler` is truthy

* Updating options interface and definitions

* Testing express error handler

* Test spec fixes

* Fix test
2018-07-17 19:47:00 +00:00
Ross Bayer ced6b76ef5 Support incrementing push badge value by more than 1 (#4889)
* Support 'IncrementByN' badge value for higher push badge increments

* Fix test

* Rely on object for badge incrementation (i.e. {increment: 3}) rather than string (IncrementBy3)

* For badge incrementation, utilize format similar to other operation notation
2018-07-12 20:34:08 +02:00
greenkeeper[bot] 800959f458 Update flow-bin to the latest version 🚀 (#4877)
* chore(package): update flow-bin to version 0.76.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-12 14:23:05 +00:00
greenkeeper[bot] 8b571a3b54 Update bcrypt to the latest version 🚀 (#4875)
* chore(package): update bcrypt to version 3.0.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-12 13:59:52 +00:00
Florent Vilmart c1e32d853f Greenkeeper/nodemon 1.18.1 (#4891)
* chore(package): update nodemon to version 1.18.1

Closes #4885

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-12 13:59:06 +00:00
greenkeeper[bot] cad81f83e3 Update ws to the latest version 🚀 (#4888)
* fix(package): update ws to version 5.2.2

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-12 13:57:52 +00:00
greenkeeper[bot] e07a442362 Update mongodb to the latest version 🚀 (#4874)
* fix(package): update mongodb to version 3.1.1

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-06 06:18:43 -04:00
Florent Vilmart 01323abbc8 Greenkeeper/flow bin 0.75.0 (#4872)
* chore(package): update flow-bin to version 0.75.0

Closes #4816

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-04 11:52:31 -04:00
greenkeeper[bot] 3819fe8367 Update mongodb-runner to the latest version 🚀 (#4869)
* chore(package): update mongodb-runner to version 4.0.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-03 11:14:26 -04:00
Florent Vilmart 960431b92d Even faster tests (#4868)
* Various improvements in test name / de-duplications

* Reverts to class by class deletion, introduced fast mode that just delete data for mongo

- Speeds up are incredible Executed 1695 of 1713 specs INCOMPLETE (18 PENDING) in 4 mins 19 secs.

* Adds documentation about the deleteEverything
2018-07-03 11:13:08 -04:00
Florent Vilmart ae1a8226d5 Removes need to use babel-register (#4865)
* Removes need to use babel-register

- Adds watch to watch changes when running the test to regenerate
- Tests are now pure node 8

* Adds timing to helper.js

* Update contribution guide

* Adds inline sourcemaps generation to restore coverage

* nits
2018-07-02 23:30:14 -04:00
Florent Vilmart 74740abead Update issue templates (#4867) 2018-07-02 22:15:55 -04:00
greenkeeper[bot] ed4a436860 Update pg-promise to the latest version 🚀 (#4866)
* fix(package): update pg-promise to version 8.4.5

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-07-02 20:47:17 -04:00
Florent Vilmart e97df073df Testing improvements (#4863)
* Adds supports-color so spec reporter dont output colors when not supported

* Adds babelrc so breakpoints dont jump around in specs

* Rename test to avoid duplicated name

* Removes unused methods

* Better naming for it_exclude
2018-07-01 00:00:37 -04:00
Florent Vilmart 585938ca2c improves test performance on mongodb (#4862)
* improves test performance on mongodb

* Removes unused methdos
2018-06-29 17:09:51 -04:00
greenkeeper[bot] 1e8ba742a8 Update commander to the latest version 🚀 (#4861)
* fix(package): update commander to version 2.16.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-29 08:12:11 -04:00
Florent Vilmart 3bcb5a0997 Ensure User ACL's are more flexible and secure #3588 (#4860)
* Fixes an issue that would let the beforeDelete be called when user has no access to the object

* Ensure we properly lock user

- Improves find method so we can attempt to read for a write poking the right ACL instead of using masterKey
- This ensure we do not run beforeDelete/beforeFind/beforeSave in the wrong scenarios

* nits

* Caps insufficient
2018-06-28 16:31:22 -04:00
Florent Vilmart 9e5d26ebd2 Removes unused package multer, fixes audit (#4859) 2018-06-28 09:22:48 -04:00
greenkeeper[bot] d2e3459206 Update cross-env to the latest version 🚀 (#4830)
* chore(package): update cross-env to version 5.2.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-28 08:07:35 -04:00
greenkeeper[bot] 5add9584a3 Update multer to the latest version 🚀 (#4858)
* fix(package): update multer to version 1.3.1

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-28 08:06:14 -04:00
greenkeeper[bot] b332f4d4ae Update mongodb to the latest version 🚀 (#4855)
* fix(package): update mongodb to version 3.1.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile

* starting mongo 3.1.0, read preferences are passed again

* Adds test confirming #4831 is properly functional now
2018-06-27 14:12:51 -04:00
Chris 5cdbde244a #4678: Converting strings to Date when schema.type is Date within agg… (#4743)
* #4678: Converting strings to Date when schema.type is Date within aggregate function

* Added test cases to test new date match aggregate query

* Added function to parse match aggregate arguments and convert necessary values to Date objects

* Added missing return value

* Improved code quality based on suggestions and figured out why tests were failing

* Added tests from @dplewis

* Supporting project aggregation as well as exists operator

* Excluding exists match for postgres

* Handling the $group operator similar to $match and $project

* Added more tests for better code coverage

* Excluding certain tests from being run on postgres

* Excluding one more test  from postgres

* clean up
2018-06-26 17:18:04 -05:00
greenkeeper[bot] 3ace26da6f Update eslint to the latest version 🚀 (#4848)
* chore(package): update eslint to version 5.0.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-25 21:13:10 -04:00
Florent Vilmart e15244c7b2 Removes npm-git.sh scripts (#4853) 2018-06-25 20:59:54 -04:00
greenkeeper[bot] ade5a33c27 Update ws to the latest version 🚀 (#4849)
* fix(package): update ws to version 5.2.1

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-25 15:04:11 -04:00
Diamond Lewis 64388a31ad Aggregate allow multiple of same stage (#4835)
* Aggregate Allow Multiple Stages

* remove testing files

* nit

* spread them
2018-06-23 11:28:17 -05:00
Diamond Lewis 0e93306867 nit 2018-06-16 10:16:54 -07:00
Diamond Lewis 00252cbeff add includeAll option 2018-06-16 10:16:54 -07:00
Johnny 2ef437a2bc Added verify password to users router and tests. (#4747)
* Added verify password to users router and tests.

* Added more tests to support more coverage.

* Added additional tests to spec. Removed condition from verifyPassword function where authData null keys condition wasn't necessary.

* Removed POST handling from verifyPassword.

* Refactored handleLogin and handleVerifyPassword to use shared helper function to validate the password provided in the request.

* Refactored verifyPassword and login to not use try/catch. Parent promise returns the error. Moved login specific functions to login handler.

* Added account lockout policy to verify password function. Added test spec for account lockout in verify password.

* no message

* Merged new changes from master. Made changes as requested from comments.

* We cannot remove hidden properties from the helper before returning to the login function. The password expiration check in the login function is dependent on some hidden properties, otherwise three password policy tests fail.
2018-06-13 14:19:53 -04:00
Florent Vilmart 9c9c07fb26 Ensure read preference is never overriden, so DB config prevails (#4833) 2018-06-13 13:51:29 -04:00
Manuel 1e29d0299b add support for geoWithin.centerSphere queries via withJSON (#4825)
* add support for geoWithin.centerSphere queries via withJSON

* added test for passing array of lat, lng instead of Parse.GeoPoint

* added postgres support

* added more tests

* improved tests and validation

* added more tests
2018-06-12 12:41:02 -04:00
Arthur Cinader 260c466dcb Change name of function and variable from baseFieldName to rootFieldName (#4817) 2018-06-07 19:27:11 -04:00
Arthur Cinader e06471603f Don't error when attempting to sort on an object field (#4806)
* add failing test to demonstrate that you can't sort on a
field in an object.

* Only validate the base of the field name.

* fix test name

* Only test sort for mongo.

* pg order by nested object

* level 2 test

* Factor out operation to get a field's base name.  Add comment.

* tweak comment wording so it wont make my grammar teacher angry.
2018-06-07 18:47:18 -04:00
Jaeger cf3a872e67 Don't merge JSON fields after save() when using Postgres to keep same behaviour as MongoDB (#4808) (#4815) 2018-06-07 12:43:54 -04:00
Florent Vilmart 1afc34eea3 chore(package): update nyc to version 12.0.2 (#4807)
* chore(package): update nyc to version 12.0.2

Closes #4804

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-04 15:22:29 -04:00
Florent Vilmart 0c2f1d583b ⚡ Release 2.8.2 (#4802) 2018-06-04 13:07:52 -04:00
greenkeeper[bot] 3b40d2227d Update flow-bin to the latest version 🚀 (#4786)
* chore(package): update flow-bin to version 0.73.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-06-01 09:32:33 -04:00
Tyler Brock c5e8470e36 Always use http-agent for hooks and triggers (#4800) 2018-06-01 09:31:10 -04:00
Florent Vilmart 9bff44b446 Ensure users with undefined ACL are treated as readable (#4795)
* Adds test to reproduce issue #4790

* Attempt to allow failure on node STABLE

* Use new format for apt packages
2018-05-30 12:55:15 -04:00
Tyler Brock f4422c491e Update defs for new hookKeepAlive config option (#4794)
All tests pass on re-run. :-)
2018-05-29 17:30:26 -07:00
Tyler Brock 82fb06f9c9 Use http agents for hook requests (#4791) 2018-05-29 17:48:06 -04:00
greenkeeper[bot] 32d8c36ff5 Update cross-env to the latest version 🚀 (#4780)
* chore(package): update cross-env to version 5.1.6

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-05-24 16:28:37 -04:00
greenkeeper[bot] fd862950e2 Update nodemon to the latest version 🚀 (#4781)
* chore(package): update nodemon to version 1.17.5

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-05-24 16:28:17 -04:00
Mads Bjerre 57da2def1d Adds withinPolygon support for Polygon object (#4067)
* Whitespace

* Add Polygon type to $polygon query

* Add tests

Polygon object in $polygon query
$geoIntersects queries

* Refactor

* Postgres support

* More tests

* Remove duplicate test

* Missing semicolon

* fix tests
2018-05-22 12:06:43 -04:00
Florent Vilmart 4dcbc4245b fix(package): update ws to version 5.2.0 (#4779)
* fix(package): update ws to version 5.2.0

* chore(package): update lockfile

https://npm.im/greenkeeper-lockfile
2018-05-21 21:43:50 -04:00
Florent Vilmart 642e2ffe12 Adds greenkeeper package-lock management (#4778) 2018-05-21 17:38:54 -04:00
greenkeeper[bot] d2b04ddd92 chore(package): update gaze to version 1.1.3 (#4776) 2018-05-21 15:45:10 -04:00
greenkeeper[bot] 33173c4d9b fix(package): update deepcopy to version 1.0.0 (#4775) 2018-05-20 15:13:17 -04:00
Florent Vilmart ff0c04ffa3 chore(package): update deep-diff to version 1.0.1 (#4774)
Closes #4733
2018-05-19 11:24:58 -04:00
greenkeeper[bot] df274dcca9 fix(package): update commander to version 2.15.1 (#4663) 2018-05-18 15:49:40 -04:00
Florent Vilmart a619119684 Bumps minimum node engine to 8+ (#4474)
* Use node 8 as development environment

* fixup! Use node 8 as development environment

* bump node to 8.10

* Targets node 8 for everything

* Run npm install so lock file is up to date

* Use push adapter v3

* Deflake a test on ParseUser

* Adds slight delay after logout

* Ensure we wait even if call fails

* Use node carbon
2018-05-18 15:49:00 -04:00
Jérémy Piednoel 77ed10f57b Adding $nor operator support (#4768)
* adding nor to specialQuerykeys

* adding nor suport

* adding test

* CRLF

* adding postgres NOR

* adding nor validation

* adding NOR test

* adding test amd fixing NOR cases

* revert the nor logic
2018-05-18 14:26:33 -05:00
Florent Vilmart a9ebc2b9af ⚡ Release 2.8.1 (#4771)
* unfuck release 2.8.0

* ⚡ Release 2.8.1

* Ensure we can install also on latest
2018-05-18 13:06:12 -04:00
Florent Vilmart 8fe3265c74 Release 2.8.0 (#4767)
* Ensure issue template has proper line endings

* Bumps to 2.8.0

* Adds Changelog
2018-05-18 10:55:20 -04:00
Saulo Tauil 95550f4c77 Making response.error accept a Parse.Error object as parameter (#4695) 2018-05-18 10:15:25 -04:00
Florent Vilmart fc47f9b84f issue(afterFind): Fixes issue when using afterFind with relations (#4752)
* Adds failing test for the issue

* Adds fix for the issue
2018-05-18 09:36:56 -04:00
Jérémy Piednoel c0f86ae1d1 Adding Mongodb element to add arrayMatches the #4762 (#4766)
* Adding elemMatch and nor

* lint

* adding test

* adding edge test

* postgres support

* clean up

* empty test
2018-05-18 08:35:50 -05:00
Florent Vilmart ad244d6654 feature: User Lockout (#4749)
* Allows masterKey to lock _User object and prevent login with email / password

* Ensure the authData based auth can be locked out as well when accounts is masterKey only
2018-05-16 15:40:02 -04:00
greenkeeper[bot] bfd0c4bf2f chore(package): update cross-env to version 5.1.5 (#4757) 2018-05-16 15:39:40 -04:00
greenkeeper[bot] d36c7c7f90 chore(package): update uws to version 10.148.0 (#4758) 2018-05-16 15:39:29 -04:00
greenkeeper[bot] 10232673d9 fix(package): update body-parser to version 1.18.3 (#4763) 2018-05-16 15:39:10 -04:00
Eduard Bosch Bertran c0e3672e32 New query condition support to match all strings that starts with some other given strings (#3864)
* feat: Convert $regex value to RegExp object

* feat: Add lib folder

* Revert "feat: Add lib folder"

This reverts commit c9dfbcbf69.

* feat: Add $regex test in $all array

* test: Test regex with $all only in MongoDB

* Revert "test: Test regex with $all only in MongoDB"

This reverts commit d7194c7869.

* feat: Add tests for containsAllStartingWith

* feat: Add postgres support

Thanks to @dplewis

* feat: Check that all values in $all must be regex or none

* test: Check that $all vaules must be regex or none

* feat: Update tests to use only REST API

* refactor: Move $all regex check to adapter

* feat: Check for valid $all values in progres

* refactor: Update function name

* fix:  Postgres $all values regex checking

* fix: Check starts with as string

* fix: Define contains all regex sql function

* fix: Wrong value check

* fix: Check valid data

* fix: Check regex when there is only one value

* fix: Constains all starting with string returns empty with bad params

* fix: Pass correct regex value

* feat: Add missing tests

* feat: Add missing tests

* feat: Add more tests

* fix: Unify MongoDB and PostgreSQL functionality

* fix: Lint checks

* fix: Test broken

$regex in $all list must be { $regex: "string" }

* test for empty $all
2018-05-15 20:42:32 -05:00
Joseph Frazier 2c357df33e Fix typo: "requrest" -> "request" (#4761) 2018-05-13 13:49:07 -04:00
Florent Vilmart 3999de7e0b Update issue templates 2018-05-08 16:40:07 -04:00
Florent Vilmart a3e5b20536 Bumps @parse/simple-mailgun-adapter (#4753) 2018-05-08 13:48:51 -04:00
Florent Vilmart bb1641419f fix(package): update mime to version 2.3.1 (#4721)
Closes #4716
2018-05-01 14:02:56 -04:00
Florent Vilmart 0b69700f18 chore(package): update flow-bin to version 0.71.0 (#4746)
Closes #4730
2018-05-01 13:32:12 -04:00
greenkeeper[bot] ac2695e196 chore(package): update deep-diff to version 1.0.0 (#4733) 2018-05-01 13:31:47 -04:00
Florent Vilmart 098d085621 fix(package): update request to version 2.85.0 (#4745)
Closes #4634
2018-05-01 13:31:02 -04:00
Vitaly Tomilov a1f5f37af7 upgrading pg-promise to 8.4.0 (#4736) 2018-05-01 13:27:29 -04:00
greenkeeper[bot] d48b30c734 fix(package): update mongodb to version 3.0.7 (#4732) 2018-05-01 13:26:04 -04:00
greenkeeper[bot] 3b5dff195f chore(package): update bcrypt to version 2.0.1 (#4735) 2018-05-01 13:25:37 -04:00
Florent Vilmart fad7b46c65 chore: Fixes issue related to flow types (#4724)
* Fixes issue related to flow types

* Improves type inference on where clause, index

* run parse-server start on liveQueryServer only on mongo, state leaks on postgres
2018-05-01 07:37:38 -04:00
Claire Neveu 04588bccb9 Allow creation of indices on default fields (#4738)
* Allow creation of indexes on default fields

* Update test

* Try to address flaky cache test
2018-04-25 16:06:10 -07:00
oleg b1017ac17c Send version for Vkontakte API (#4725) 2018-04-15 15:09:28 -04:00
Florent Vilmart ebf65bfc5d Updates @parse/push-adapter (#4723) 2018-04-15 09:52:18 -04:00
dblythy 22801d2d8f Ensure we respond with invalid password even if email is unverified (#4708) 2018-04-11 15:39:32 -04:00
Florent Vilmart 8380107e6b chore(package): update nodemon to version 1.17.3 (#4712)
Closes #4637
2018-04-10 13:57:11 -04:00
Florent Vilmart a77b0a238f chore(package): update flow-bin to version 0.69.0 (#4711)
Closes #4651
2018-04-10 13:56:46 -04:00
Florent Vilmart 6c81635188 fix(package): update mongodb to version 3.0.6 (#4710)
Closes #4674
2018-04-10 13:56:05 -04:00
Florent Vilmart fed40128a5 fix(package): update mime to version 2.2.2 (#4709)
Closes #4687
2018-04-10 13:55:21 -04:00
greenkeeper[bot] ee151de56a fix(package): update ws to version 5.1.1 (#4691) 2018-04-10 13:55:02 -04:00
Jong Eun Lee b0bcbd8731 add _password_history to default sensitive data (#4699) 2018-04-10 13:54:35 -04:00
greenkeeper[bot] 06be78802e chore(package): update bcrypt to version 2.0.0 (#4703) 2018-04-10 13:53:49 -04:00
Diamond Lewis 0fd8493929 Fix purging empty class (#4676)
* Fix purging empty class

* remove fit
2018-03-25 08:41:25 -05:00
Diamond Lewis 710848938f PG: Fixes issues comparing to zero or false (#4667)
* initial test

* more test

* clean up

* added correct errors

* test fix

* $eq null test

* type check
2018-03-24 17:45:34 -05:00
Samarth Verma 1a5c05b6a8 Fix organization url (#4639) 2018-03-21 14:54:08 -04:00
greenkeeper[bot] 306826fe0f fix(package): update ws to version 5.1.0 (#4662) 2018-03-21 14:52:50 -04:00
Diamond Lewis a3230011eb Check for node version in postinstall script (#4657)
Added open collective as a bonus.

I notice a lot of user still fork parse-server-example. They then update to the latest version but their engines.node in package.json stays the same.

This will output directions to support latest version.
2018-03-16 16:09:00 -05:00
SebC 6abf29dd97 Remove FB Graph API version from URL to use the oldest non deprecated version (2.5 for now, which will be deprecated soon) (#4650) 2018-03-15 14:05:41 -05:00
Diamond Lewis c36ef99368 Fix Aggregate Match Pointer (#4643) 2018-03-14 21:57:30 -05:00
Florent Vilmart 3acb3e7a9b ⚡ Release 2.7.4 (#4626)
* ⚡ Release 2.7.4

* Update package.json

* Update CHANGELOG.md
2018-03-10 16:40:59 -05:00
greenkeeper[bot] d852cade9c fix(package): update pg-promise to version 8.2.1 (#4624) 2018-03-10 16:23:07 -05:00
Diamond Lewis c6bc81caef Mongo: Fix reversing polygon coordinates (#4609)
* Fix reversing polygon coordinates

* comments fix

* real data test

* improved tests
2018-03-10 14:27:03 -06:00
Florent Vilmart c7235829c3 chore(package): update nodemon to version 1.17.1 (#4623)
Closes #4578
2018-03-10 12:03:17 -05:00
310 changed files with 76994 additions and 32185 deletions
+6 -5
View File
@@ -1,13 +1,14 @@
{
"plugins": [
"transform-flow-strip-types",
"transform-object-rest-spread"
"@babel/plugin-transform-flow-strip-types",
"@babel/plugin-proposal-object-rest-spread"
],
"presets": [
["env", {
["@babel/preset-env", {
"targets": {
"node": "6.11.4"
"node": "8"
}
}]
]
],
"sourceMaps": "inline"
}
+1 -1
View File
@@ -1,3 +1,3 @@
lib
coverage
out
+4 -2
View File
@@ -14,7 +14,7 @@
"sourceType": "module"
},
"rules": {
"indent": ["error", 2],
"indent": ["error", 2, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"no-trailing-spaces": 2,
"eol-last": 2,
@@ -22,6 +22,8 @@
"no-multiple-empty-lines": 1,
"prefer-const": "error",
"space-infix-ops": "error",
"no-useless-escape": "off"
"no-useless-escape": "off",
"no-prototype-builtins": "off",
"require-atomic-updates": "off"
}
}
+8 -8
View File
@@ -1,30 +1,30 @@
<!--
**Please use this template. If you don't use this template, your issue may be closed without comment.**
We use GitHub Issues for bugs.
If you have a non-bug question, ask on Stack Overflow or Server Fault:
- https://stackoverflow.com/questions/tagged/parse.com
- https://serverfault.com/tags/parse
If you have a non-bug question, ask on [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
-->
### Issue Description
Describe your issue in as much detail as possible.
<!-- Describe your issue in as much detail as possible. -->
### Steps to reproduce
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
<!-- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. -->
#### Expected Results
What you expected to happen.
<!-- What you expected to happen. -->
#### Actual Outcome
What is happening instead.
<!-- What is happening instead. -->
### Environment Setup
@@ -0,0 +1,17 @@
---
name: "\U0001F4A1 Feature request"
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,5 @@
---
name: "🙋‍Getting Help"
about: Join https://community.parseplatform.org
---
@@ -0,0 +1,56 @@
---
name: "\U0001F525 parse-server 3.0.0"
about: Report an issue while migrating to parse-server 3.0.0
---
<!---
** Use this template only if you have an issue migrating to parse-server 3.0.0 **
If you have a question, ask on [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
--->
# Before opening the issue please ensure that you have:
- [ ] [Read the migration guide](https://github.com/parse-community/parse-server/blob/master/3.0.0.md) to parse-server 3.0.0
- [ ] [Read the migration guide](https://github.com/parse-community/Parse-SDK-JS/blob/master/2.0.0.md) to Parse SDK JS 2.0.0
### Issue Description
<!--- Describe your issue in as much detail as possible. -->
### Steps to reproduce
<!--- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. --->
### Expected Results
<!--- What you expected to happen. --->
### Actual Outcome
<!--- What is happening instead. --->
### Environment Setup
- **Server**
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
- Operating System: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
- **Database**
- MongoDB version: [FILL THIS OUT]
- Storage engine: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
### Logs/Trace
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
@@ -0,0 +1,49 @@
---
name: "\U0001F4F2 Push Notifications"
about: Issues with setting up or delivering push notifications
---
<!---
**We use GitHub Issues for reporting bugs with parse-server.**
If you have a *question*, you should join the [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
If you have a non-bug question, ask on Stack Overflow or Server Fault:
- https://stackoverflow.com/questions/tagged/parse.com
- https://serverfault.com/tags/parse
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
--->
### Issue Description
<!--- Describe your issue in as much detail as possible. -->
### Push Configuration
Please provide a copy of your `push` configuration here, obfuscating any sensitive part.
### Environment Setup
- **Server**
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
- Operating System: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
- **Database**
- MongoDB version: [FILL THIS OUT]
- Storage engine: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
### Logs/Trace
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
@@ -0,0 +1,51 @@
---
name: "\U0001F41B Report an issue"
about: Report an issue on parse-server
---
<!---
**We use GitHub Issues for reporting bugs with parse-server.**
If you have a *question*, you should join the [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server).
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
--->
### Issue Description
<!--- Describe your issue in as much detail as possible. -->
### Steps to reproduce
<!--- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. --->
### Expected Results
<!--- What you expected to happen. --->
### Actual Outcome
<!--- What is happening instead. --->
### Environment Setup
- **Server**
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
- Operating System: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
- **Database**
- MongoDB version: [FILL THIS OUT]
- Storage engine: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
### Logs/Trace
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
+24
View File
@@ -0,0 +1,24 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 45
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- good first issue
- up-for-grabs
- bug
- enhancement
- help wanted
# Label to use when marking an issue as stale
staleLabel: wontfix
# Limit to only `issues` not `pulls`
only: issues
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+4
View File
@@ -15,6 +15,10 @@ lib-cov
coverage
.nyc_output
# docs output
out
docs
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
+2
View File
@@ -0,0 +1,2 @@
10.14.2
+1 -2
View File
@@ -4,8 +4,7 @@
"text-summary"
],
"exclude": [
"**/spec/**",
"lib/"
"**/spec/**"
]
}
+3
View File
@@ -0,0 +1,3 @@
semi: true
trailingComma: "es5"
singleQuote: true
+45 -41
View File
@@ -1,75 +1,79 @@
language: node_js
dist: trusty
services:
- mongodb
- postgresql
- redis-server
- docker
- mongodb
- postgresql
- redis-server
- docker
addons:
postgresql: '9.5'
apt_packages:
- postgresql-9.5-postgis-2.3
apt:
packages:
- postgresql-9.5-postgis-2.3
branches:
only:
- master
- /^[0-9]+.[0-9]+.[0-9]+(-.*)?$/
- "/^[0-9]+.[0-9]+.[0-9]+(-.*)?$/"
- 3.x
- /^greenkeeper/.*$/
- "/^greenkeeper/.*$/"
cache:
directories:
- node_modules
- "$HOME/.npm"
- "$HOME/.mongodb/versions"
# Test stage
stage: test
env:
global:
- COVERAGE_OPTION='./node_modules/.bin/nyc'
- NODE_VERSION=6.11.4
matrix:
- MONGODB_VERSION=3.2.13
- MONGODB_VERSION=3.4.4
- MONGODB_VERSION=4.0.4
- MONGODB_VERSION=3.6.9
- PARSE_SERVER_TEST_DB=postgres
- PARSE_SERVER_TEST_CACHE=redis
- NODE_VERSION=8.7
- NODE_VERSION=11.4.0
matrix:
allow_failures:
- env: NODE_VERSION=11.4.0
before_install:
- nvm install $NODE_VERSION
- nvm use $NODE_VERSION
- npm install -g greenkeeper-lockfile@1
before_script:
- node -e 'require("./lib/index.js")'
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
- silent=1 mongodb-runner --start
- greenkeeper-lockfile-update
script:
- npm run lint
- npm run coverage
after_script:
- greenkeeper-lockfile-upload
- bash <(curl -s https://codecov.io/bash)
jobs:
include:
# release on github latest branch
- stage: release
node_js: '6.11.4'
env:
before_script: skip
after_script: skip
script: skip
deploy:
- provider: script
skip_cleanup: true
script: ./resources/npm-git.sh
on:
branch:
- master
- 3.x
- provider: npm
skip_cleanup: true
email:
secure: "YweTGc22uqFWpzbfiUa5ptBLRAy6tt6d9TZLwEkPtmnsWzN9dguGyKWmXiw0qL+848FWQ5PWzUgBn5XdigV9tF3rJY6RGs8i38WulNjwSoGuRZa3AChsQHAb1KenANcJybzhnwgEj9gRsrGZPEsyI2whfake/xLDtG91kHjpJANsd4gseOh6fdS4FIYCbyXvSbC7S0yZzIJkgSkMqJO7RJ8r0HYQ7srYIw31dM3ZXSmUYu+GaMDUUu3RLAGYoKaROxhDRnzkjdeLLiwZH8hQ/6CaqwqX54iJ6OS+MOQU3fi5ZXalA7RZvtC4RmrcCkaTf3i0f+5xejYIFgzXdYGMVm8DUc82tDw1s4b6Pb19bgi1xXOQ0IKzRmZuGxnvkRN61dnYdnpnnNuG97HXgVjiOigZXVLZkWazUdnf9zXqmkC+KxPfa4Ldqg0TMjQ9J14n6TXxRti8Tt0xMa1Uzho7VdsxdJy35Bghy398O6X8VdX6parfzEkX7c/JzcA3TIMJ9+S1dy3J4Tb2URB5367W6h7cDeXtGmwLTFu75Q1CNqRJkUGbSrX2NyMqG5tc8oaTM+OWvLxcbKPRy9T6qN7x2JmCHHaapf8/8VR9wCy2PwE5j+KWhmctEHqqOgrtG5gsjC0eCPJsposxZWyM7M6aUpXe3w+olBfKq9apUGStUSU="
api_key:
secure: "QprkaqQ+WCvZQR4qIEs5iS6peMCbRd8Hgt0s6HfdmhavNXwDFY8Bkdf6zJwWHLiqs4pyClXDZ2f6QiOs7y9IvJZ+wOIbsf4N5V6s06zOxJ0NAOwhe0mgWS3Us0zgXIfmW4BpmGnU4ql/qGL+9vNfyQJ7wxEJxVK7hiYh9Epu49E2jmefDqTX+SNSrDCg4HkRcxerxYGnAJDCP50QaNlyLSciODD6wHaddrSYkdvmISLMnLHug61OkE4OBIOWXfYV+e31kDj+zgczAfVQgekDKtaimCQclHFrmaEPl0KIm6wsDQAw5HWkepA/WZfv4SbCrDaKJDZw+LBI7dR0ezmiOH/zmWWrRW7D4wjkDGiumWjA8etuf8I4GRyC/d1RS+hnlvPr0Bu+WljuVxLoK3nhZOdiK5t4QlVDoGankkRjLylwFQgo8tzu5N0dc26z3ClowTwcKsjStmFr53gjCD7l3qoFjyPot1JlW3LPhG9Nch7rK33/7ONqVai3zxb1xB9ynd8TSzKi4/66LeYEDcGVM5A9Vmkp+egCnOhkOAXdI8O4jid98NANir+U4xmUYZ2PAMNmSeHlpSpwH2pC1/BHpDKA9RZEuEdr3sgmtuXXwwOCp+xvuVwxZZ6+gVEjG4nGTqSNxUIq1fzjGih8ElJtbM1Uhh2dVE9uxW7EC/oCuuk="
on:
tags: true
all_branches: true
repo: parse-community/parse-server
- stage: release
node_js: '10'
env:
before_script: skip
after_script: skip
script:
- "./release_docs.sh"
deploy:
- provider: pages
skip_cleanup: true
github_token:
secure: YU2lUqmW036AHBRu7xO/AwEeQ900Q/5O6FL96ZqWEfD7Gadaq4iapkNvhPR0HcZYRHqQV2/2LCHVnhd0dbj0ShkmVIHdQE7O+MF+j0v0GIVc8FPTPe1/d2Hy4apSWNe6FeCrYKVeliAu+ZqvNuFLhVmYvIeJdlJrMGOb6P76UZXRDv2srXhq4uBcCVUJuTajyyd5ttJfcNapymTP+xzEDYc7Hr4LJaubmv/wVD/xwPBbvfYFuBqysUpkPKi/ODlQbB0ybYh2fFnX71WUyFUGbtB5xI9ma951Zp4v3t73c31uUl27dJaHzO62EtVTdcUKAa804EtAtsvpeJWMVWKUgigm9UZcXdEwKa79fl5nLaq34lrSktAYOkexwPqYj+vbS6sn52JrluSxLE+4cEke7tYbnJ9X12SAQXKgcXY3n30+6gKf9RVqYdlNsYpEAqKVIDb6SlEkk86dP+uIg/XXb5RKEwxbDXnb6xdl9JRc+GTbeeY3/vg2h9QTdmFVblPtBhHrNenQYP/BS0n+EfUnAIBKqRmQgyhao3SiY5FMACM8higI2Lvvhpq46pDhXqsexYCz5F008C6YXGDh5gC93rJFec0pjh55DNdQu6uw3YMQ5jtf3QUXoPAoMFud3cTulMlnjC1WZG8QbqER8dzUZ4TcaQUdzribJI/mRriheBg=
local_dir: docs/
on:
all_branches: true
- provider: npm
api_key:
secure: Yrng6jsnMAtzrrln9DwRuY4xpcxl/WYS/1A5fckyQF6DsLmNlvqVtu3MWF+zdJgANF63G3jIee11tNBpYRecHl8FjPGwO0kc1vEgvIRVnveyR0bwIIDH6s/mR9dg4rZikflwG4XExsLyaQd7ko8aTIOIayfxJiv/u0yqwuBuBW2bFrL/41b1cKGK5+Iq2a+PdFENUPenKXISkACGaMnQF4Y/KVF98UwCiGLf957yFWc2sD6TFbjNDbAENSccvg1J3fBb+djbtzKzldl29ntp2mKVGSVASiKCRa6hSfgQulHiidFqFIKgpYJ1uATRr3UFr/NRVt1WbrgLnzY74OCX7y02c/xiYQMMEPRl/P8hHJu+KjQ3PBWsBvmsRN0QMUJv3PEjUPE3AY8sw49NoxiJZzJr7574vUBuM/dk0byYI6K/gwmUrPIhQjljzZqinS8JJJ4FjGjCdzXRhT5Q+PZvt5bF1rMOZXayNJZa+cICtmiJoU3vO2Yf6TXC3wY1veKvmcs24nws5lp4keJePTKAi1Ig7VoSxwAlgs3EGANIh7oBKx9zWnXQYMdmxbYsvLXAWcnnM+PcSCvooLmXWso3BQBeRuUUSS2oLaBpsFMsiniLNbA1cW4fwMkgUGz4oEDRi8wTD89E/1J2oNxzqtWRPawcVKpMpnBbDJHrWJPEHMw=
email:
secure: lomzDl71N995SzRczm7VE9OZE+PzMo4X8t3zU97agu/FMH5Qcj8BLwE+uVDTnA9Vblyj62ZsFKsNjP2Qp53Vcd+jHM4EJNWNRZYpEMIRO3LngX43r83qoFEHUvPu9s1oaa04a6FojcsJx0wl6B6Ke2AX74MXnJDLb9iZBy1mkpLUMVccVhSfhdoIzhkq3dhUw+6d8C024tNMHcgDW3VnRAsWFtiL7dCMpjLOdI+UxlkeGkQkxXXuRsZ0ZdjoSoM8NSkiYMc8x6EnekyRDoHTujX3OFxuU6+GAjrUmVzNmJWrBIqHVb0DXBQxjEaG3d/cNu5UsQyZYq5sxRRH0BaLs7F4oIQg95etasEtTtUkmsZ3pshVlsweiLU366UdbfuAf5hrJjqLrU12BKZyLjaAwyeKz031r8dA4sJtGIp5uVdXobQQTH6r958A88byJ20uaYSqhqjhZo3hkWXIQP0WQN2Ej/g57HbVNLB/nPKkMILfk/tpp7nBDLT0QrjbZxeo1dwCHqsBEV6z7ZyWyFf4xwpDsir4txL4t8ElzeGdlACjCqAJvIh5w9YzfrwijtoVMvvP6pWvn/iI640d4rsdIDe8egxgqZ1R/TMd/tdHYX+eI+ZfFmCVGj36/uXwdG7KIoIZVjRQ2tvWr9ZuydEPWPSRVGT4ycFeu6wbm3elM3I=
skip_cleanup: true
on:
tags: true
all_branches: true
repo: parse-community/parse-server
+226
View File
@@ -0,0 +1,226 @@
# Upgrading Parse Server to version 3.0.0
parse-server 3.0.0 comes also with the JS SDK version 2.0 which requires a migration. Follow the migration guide [here](https://github.com/parse-community/Parse-SDK-JS/blob/master/2.0.0.md).
With the 3.0.0, parse-server has completely revamped it's cloud code interface. Gone are the backbone style calls, welcome promises and async/await.
In order to leverage those new nodejs features, you'll need to run at least node 8.10. We've dropped the support of node 6 a few months ago, so if you haven't made the change yet, now would be a really good time to take the plunge.
## Migrating Cloud Code Hooks
### Synchronous validations:
```js
// before
Parse.Cloud.beforeSave('MyClassName', function(request, response) {
if (!passesValidation(request.object)) {
response.error('Ooops something went wrong');
} else {
response.success();
}
});
// after
Parse.Cloud.beforeSave('MyClassName', (request) => {
if (!passesValidation(request.object)) {
throw 'Ooops something went wrong';
}
});
```
All methods are wrapped in promises, so you can freely `throw` `Error`, `Parse.Error` or `string` to mark an error.
### Asynchronous validations:
For asynchronous code, you can use promises or async / await.
Consider the following beforeSave call that would replace the contents of a fileURL with a proper copy of the image as a Parse.File.
```js
// before
Parse.Cloud.beforeSave('Post', function(request, response) {
Parse.Cloud.httpRequest({
url: request.object.get('fileURL'),
success: function(contents) {
const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') });
file.save({
success: function() {
request.object.set('file', file);
response.success();
},
error: response.error
});
},
error: response.error
});
});
```
As we can see the current way, with backbone style callbacks is quite tough to read and maintain.
It's also not really trivial to handle errors, as you need to pass the response.error to each error handlers.
Now it can be modernized with promises:
```js
// after (with promises)
Parse.Cloud.beforeSave('MyClassName', (request) => {
return Parse.Cloud.httpRequest({
url: request.object.get('fileURL')
}).then((contents) => {
const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') });
request.object.set('file', file);
return file.save();
});
});
```
And you can even make it better with async/await.
```js
// after with async/await
Parse.Cloud.beforeSave('MyClassName', async (request) => {
const contents = await Parse.Cloud.httpRequest({
url: request.object.get('fileURL')
});
const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') });
request.object.set('file', file);
await file.save();
});
```
## Aborting hooks and functions
In order to abort a `beforeSave` or any hook, you now need to throw an error:
```js
// after with async/await
Parse.Cloud.beforeSave('MyClassName', async (request) => {
// valid, will result in a Parse.Error(SCRIPT_FAILED, 'Something went wrong')
throw 'Something went wrong'
// also valid, will fail with Parse.Error(SCRIPT_FAILED, 'Something else went wrong')
throw new Error('Something else went wrong')
// using a Parse.Error is also valid
throw new Parse.Error(1001, 'My error')
});
```
## Migrating Functions
Cloud Functions can be migrated the same way as cloud code hooks.
In functions, the response object is not passed anymore, as it is in cloud code hooks
Continuing with the image downloader example:
```js
// before
Parse.Cloud.define('downloadImage', function(request, response) {
Parse.Cloud.httpRequest({
url: request.params.url,
success: function(contents) {
const file = new Parse.File(request.params.name, { base64: contents.buffer.toString('base64') });
file.save({
success: function() {
response.success(file);
},
error: response.error
});
},
error: response.error
});
});
```
You would call this method with:
```js
Parse.Cloud.run('downloadImage',{
url: 'https://example.com/file',
name: 'my-file'
});
```
To migrate this function you would follow the same practices as the ones before, and we'll jump right away to the async/await implementation:
```js
// after with async/await
Parse.Cloud.define('downloadImage', async (request) => {
const {
url, name
} = request.params;
const response = await Parse.Cloud.httpRequest({ url });
const file = new Parse.File(name, { base64: response.buffer.toString('base64') });
await file.save();
return file;
});
```
## Migrating jobs
As with hooks and functions, jobs don't have a status object anymore.
The message method has been moved to the request object.
```js
// before
Parse.Cloud.job('downloadImageJob', function(request, status) {
var query = new Parse.Query('ImagesToDownload');
query.find({
success: function(images) {
var done = 0;
for (var i = 0; i<images.length; i++) {
var image = images[i];
status.message('Doing '+image.get('url'));
Parse.Cloud.httpRequest({
url: image.get('url'),
success: function(contents) {
status.message('Got '+image.get('url'));
const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') });
file.save({
success: function() {
status.message('Saved '+image.get('url'));
done++;
if (done == images.length) {
status.success();
}
},
error: function() {
response.message('Error '+image.get('url'));
done++;
if (done == images.length) {
status.error();
}
}
});
},
error: status.error
});
}
}
});
});
```
```js
Parse.Cloud.job('downloadImageJob', async function(request) {
const query = new Parse.Query('ImagesToDownload');
const images = await query.find();
const promises = images.map(async (image) => {
request.message('Doing ' + image.get('url'));
const contents = await Parse.Cloud.httpRequest({
url: image.get('url')
});
request.message('Got ' + image.get('url'));
const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') });
await file.save();
request.message('Saved ' + image.get('url'));
});
await Promise.all(promises);
});
```
As you can see the new implementation is more concise, easier to read and maintain.
If you encounter a problem or discover an issue with this guide, or with any Parse Community project, feel free to [reach out on github](https://github.com/parse-community/parse-server/issues/new/choose)
+315 -3
View File
@@ -1,7 +1,319 @@
## Parse Server Changelog
### master
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.2...master)
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.7.0...master)
### 3.7.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.6.0...3.7.0)
- FIX: Prevent linkWith sessionToken from generating new session [#5801](https://github.com/parse-community/parse-server/pull/5801), thanks to [Diamond Lewis](https://github.com/dplewis)
- GraphQL: Improve session token error messages [#5753](https://github.com/parse-community/parse-server/pull/5753), thanks to [Douglas Muraoka](https://github.com/douglasmuraoka)
- NEW: GraphQL { functions { call } } generic mutation [#5818](https://github.com/parse-community/parse-server/pull/5818), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: GraphQL Custom Schema [#5821](https://github.com/parse-community/parse-server/pull/5821), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: GraphQL custom schema on CLI [#5828](https://github.com/parse-community/parse-server/pull/5828), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: GraphQL @mock directive [#5836](https://github.com/parse-community/parse-server/pull/5836), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- FIX: GraphQL _or operator not working [#5840](https://github.com/parse-community/parse-server/pull/5840), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: Add "count" to CLP initial value [#5841](https://github.com/parse-community/parse-server/pull/5841), thanks to [Douglas Muraoka](https://github.com/douglasmuraoka)
- NEW: Add ability to alter the response from the after save trigger [#5814](https://github.com/parse-community/parse-server/pull/5814), thanks to [BrunoMaurice](https://github.com/brunoMaurice)
- FIX: Cache apple public key for the case it fails to fetch again [#5848](https://github.com/parse-community/parse-server/pull/5848), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: GraphQL Configuration Options [#5782](https://github.com/parse-community/parse-server/pull/5782), thanks to [Omair Vaiyani](https://github.com/omairvaiyani)
- NEW: Required fields and default values [#5835](https://github.com/parse-community/parse-server/pull/5835), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- FIX: Postgres safely escape strings in nested objects [#5855](https://github.com/parse-community/parse-server/pull/5855), thanks to [Diamond Lewis](https://github.com/dplewis)
- NEW: Support PhantAuth authentication [#5850](https://github.com/parse-community/parse-server/pull/5850), thanks to [Ivan SZKIBA](https://github.com/szkiba)
- FIX: Remove uws package [#5860](https://github.com/parse-community/parse-server/pull/5860), thanks to [Zeal Murapa](https://github.com/GoGross)
### 3.6.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.5.0...3.6.0)
- SECURITY FIX: Address [Security Advisory](https://github.com/parse-community/parse-server/security/advisories/GHSA-8w3j-g983-8jh5) of a potential [Enumeration Attack](https://www.owasp.org/index.php/Testing_for_User_Enumeration_and_Guessable_User_Account_(OWASP-AT-002)#Description_of_the_Issue) [73b0f9a](https://github.com/parse-community/parse-server/commit/73b0f9a339b81f5d757725dc557955a7b670a3ec), big thanks to [Fabian Strachanski](https://github.com/fastrde) for identifying the problem, creating a fix and following the [vulnerability disclosure guidelines](https://github.com/parse-community/parse-server/blob/master/SECURITY.md#parse-community-vulnerability-disclosure-program)
- NEW: Added rest option: excludeKeys [#5737](https://github.com/parse-community/parse-server/pull/5737), thanks to [Raschid J.F. Rafeally](https://github.com/RaschidJFR)
- FIX: LiveQuery create event with fields [#5790](https://github.com/parse-community/parse-server/pull/5790), thanks to [Diamond Lewis](https://github.com/dplewis)
- FIX: Generate sessionToken with linkWith [#5799](https://github.com/parse-community/parse-server/pull/5799), thanks to [Diamond Lewis](https://github.com/dplewis)
### 3.5.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.4...3.5.0)
- NEW: GraphQL Support [#5674](https://github.com/parse-community/parse-server/pull/5674), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
[GraphQL Guide](https://github.com/parse-community/parse-server#graphql)
- NEW: Sign in with Apple [#5694](https://github.com/parse-community/parse-server/pull/5694), thanks to [Diamond Lewis](https://github.com/dplewis)
- NEW: AppSecret to Facebook Auth [#5695](https://github.com/parse-community/parse-server/pull/5695), thanks to [Diamond Lewis](https://github.com/dplewis)
- NEW: Postgres: Regex support foreign characters [#5598](https://github.com/parse-community/parse-server/pull/5598), thanks to [Jeff Gu Kang](https://github.com/JeffGuKang)
- FIX: Winston Logger string interpolation [#5729](https://github.com/parse-community/parse-server/pull/5729), thanks to [Diamond Lewis](https://github.com/dplewis)
### 3.4.4
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.3...3.4.4)
Fix: Commit changes
### 3.4.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.2...3.4.3)
Fix: Use changes in master to travis configuration to enable pushing to npm and gh_pages. See diff for details.
### 3.4.2
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.1...3.4.2)
Fix: In my haste to get a [Security Fix](https://github.com/parse-community/parse-server/security/advisories/GHSA-2479-qvv7-47qq) out, I added [8709daf](https://github.com/parse-community/parse-server/commit/8709daf698ea69b59268cb66f0f7cee75b52daa5) to master instead of to 3.4.1. This commit fixes that. [Arthur Cinader](https://github.com/acinader)
### 3.4.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.0...3.4.1)
Security Fix: see Advisory: [GHSA-2479-qvv7-47q](https://github.com/parse-community/parse-server/security/advisories/GHSA-2479-qvv7-47qq) for details [8709daf](https://github.com/parse-community/parse-server/commit/8709daf698ea69b59268cb66f0f7cee75b52daa5). Big thanks to: [Benjamin Simonsson](https://github.com/BenniPlejd) for identifying the issue and promptly bringing it to the Parse Community's attention and also big thanks to the indefatigable [Diamond Lewis](https://github.com/dplewis) for crafting a failing test and then a solution within an hour of the report.
### 3.4.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.3.0...3.4.0)
- NEW: Aggregate supports group by date fields [#5538](https://github.com/parse-community/parse-server/pull/5538) thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: API for Read Preferences [#3963](https://github.com/parse-community/parse-server/pull/3963) thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- NEW: Add Redis options for LiveQuery [#5584](https://github.com/parse-community/parse-server/pull/5584) thanks to [Diamond Lewis](https://github.com/dplewis)
- NEW: Add Direct Access option for Server Config [#5550](https://github.com/parse-community/parse-server/pull/5550) thanks to [Diamond Lewis](https://github.com/dplewis)
- FIX: updating mixed array in Postgres [#5552](https://github.com/parse-community/parse-server/pull/5552) thanks to [Diamond Lewis](https://github.com/dplewis)
- FIX: notEqualTo GeoPoint Query in Postgres [#5549](https://github.com/parse-community/parse-server/pull/5549), thanks to [Diamond Lewis](https://github.com/dplewis)
- FIX: put the timestamp back in logs that was lost after Winston upgrade [#5571](https://github.com/parse-community/parse-server/pull/5571), thanks to [Steven Rowe](https://github.com/mrowe009) and [Arthur Cinader](https://github.com/acinader)
- FIX: Validates permission before calling beforeSave [#5546](https://github.com/parse-community/parse-server/pull/5546), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
- FIX: Remove userSensitiveFields default value. [#5588](https://github.com/parse-community/parse-server/pull/5588), thanks to [William George](https://github.com/awgeorge)
- FIX: Decode Date JSON value in LiveQuery. [#5540](https://github.com/parse-community/parse-server/pull/5540), thanks to [ananfang](https://github.com/ananfang)
### 3.3.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.3...3.3.0)
- NEW: beforeLogin trigger with support for auth providers ([#5445](https://github.com/parse-community/parse-server/pull/5445)), thanks to [Omair Vaiyani](https://github.com/omairvaiyani)
- NEW: RFC 7662 compliant OAuth2 auth adapter ([#4910](https://github.com/parse-community/parse-server/pull/4910)), thanks to [Müller Zsolt](https://github.com/zsmuller)
- FIX: cannot change password when maxPasswordHistory is 1 ([#5191](https://github.com/parse-community/parse-server/pull/5191)), thanks to [Tulsi Sapkota](https://github.com/Tolsee)
- FIX (Postgres): count being very slow on large Parse Classes' collections ([#5330](https://github.com/parse-community/parse-server/pull/5330)), thanks to [CoderickLamar](https://github.com/CoderickLamar)
- FIX: using per-key basis queue ([#5420](https://github.com/parse-community/parse-server/pull/5420)), thanks to [Georges Jamous](https://github.com/georgesjamous)
- FIX: issue on count with Geo constraints and mongo ([#5286](https://github.com/parse-community/parse-server/pull/5286)), thanks to [Julien Quéré](https://github.com/jlnquere)
### 3.2.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.2...3.2.3)
- Correct previous release with patch that is fully merged
### 3.2.2
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.1...3.2.2)
- Security fix to properly process userSensitiveFields when parse-server is started with
../lib/cli/parse-server [#5463](https://github.com/parse-community/parse-server/pull/5463
)
### 3.2.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.0...3.2.1)
- Increment package.json version to match the deployment tag
### 3.2.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.3...3.2.0)
- NEW: Support accessing sensitive fields with an explicit ACL. Not documented yet, see [tests](https://github.com/parse-community/parse-server/blob/f2c332ea6a984808ad5b2e3ce34864a20724f72b/spec/UserPII.spec.js#L526) for examples
- Upgrade Parse SDK JS to 2.3.1 [#5457](https://github.com/parse-community/parse-server/pull/5457)
- Hides token contents in logStartupOptions if they arrive as a buffer [#6a9380](https://github.com/parse-community/parse-server/commit/6a93806c62205a56a8f4e3b8765848c552510337)
- Support custom message for password requirements [#5399](https://github.com/parse-community/parse-server/pull/5399)
- Support for Ajax password reset [#5332](https://github.com/parse-community/parse-server/pull/5332)
- Postgres: Refuse to build unsafe JSON lists for contains [#5337](https://github.com/parse-community/parse-server/pull/5337)
- Properly handle return values in beforeSave [#5228](https://github.com/parse-community/parse-server/pull/5228)
- Fixes issue when querying user roles [#5276](https://github.com/parse-community/parse-server/pull/5276)
- Fixes issue affecting update with CLP [#5269](https://github.com/parse-community/parse-server/pull/5269)
### 3.1.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.2...3.1.3)
- Postgres: Fixes support for global configuration
- Postgres: Fixes support for numeric arrays
- Postgres: Fixes issue affecting queries on empty arrays
- LiveQuery: Adds support for transmitting the original object
- Queries: Use estimated count if query is empty
- Docker: Reduces the size of the docker image to 154Mb
### 3.1.2
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.1...3.1.2)
- Removes dev script, use TDD instead of server.
- Removes nodemon and problematic dependencies.
- Addressed event-stream security debacle.
### 3.1.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.0...3.1.1)
#### Improvements:
* Fixes issue that would prevent users with large number of roles to resolve all of them [Antoine Cormouls](https://github.com/Moumouls) (#5131, #5132)
* Fixes distinct query on special fields ([#5144](https://github.com/parse-community/parse-server/pull/5144))
### 3.1.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.0.0...3.1.0)
#### Breaking Changes:
* Return success on sendPasswordResetEmail even if email not found. (#7fe4030)
#### Security Fix:
* Expire password reset tokens on email change (#5104)
#### Improvements:
* Live Query CLPs (#4387)
* Reduces number of calls to injectDefaultSchema (#5107)
* Remove runtime dependency on request (#5076)
#### Bug fixes:
* Fixes issue with vkontatke authentication (#4977)
* Use the correct function when validating google auth tokens (#5018)
* fix unexpected 'delete' trigger issue on LiveQuery (#5031)
* Improves performance for roles and ACL's in live query server (#5126)
### 3.0.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.4...3.0.0)
`parse-server` 3.0.0 comes with brand new handlers for cloud code. It now fully supports promises and async / await.
For more informations, visit the v3.0.0 [migration guide](https://github.com/parse-community/parse-server/blob/master/3.0.0.md).
#### Breaking changes:
* Cloud Code handlers have a new interface based on promises.
* response.success / response.error are removed in Cloud Code
* Cloud Code runs with Parse-SDK 2.0
* The aggregate now require aggregates to be passed in the form: `{"pipeline": [...]}` (REST Only)
#### Improvements:
* Adds Pipeline Operator to Aggregate Router.
* Adds documentations for parse-server's adapters, constructors and more.
* Adds ability to pass a context object between `beforeSave` and `afterSave` affecting the same object.
#### Bug Fixes:
* Fixes issue that would crash the server when mongo objects had undefined values [#4966](https://github.com/parse-community/parse-server/issues/4966)
* Fixes issue that prevented ACL's from being used with `select` (see [#571](https://github.com/parse-community/Parse-SDK-JS/issues/571))
#### Dependency updates:
* [@parse/simple-mailgun-adapter@1.1.0](https://www.npmjs.com/package/@parse/simple-mailgun-adapter)
* [mongodb@3.1.3](https://www.npmjs.com/package/mongodb)
* [request@2.88.0](https://www.npmjs.com/package/request)
##### Devevelopment Dependencies Updates:
* [@parse/minami@1.0.0](https://www.npmjs.com/package/@parse/minami)
* [deep-diff@1.0.2](https://www.npmjs.com/package/deep-diff)
* [flow-bin@0.79.0](https://www.npmjs.com/package/flow-bin)
* [jsdoc@3.5.5](https://www.npmjs.com/package/jsdoc)
* [jsdoc-babel@0.4.0](https://www.npmjs.com/package/jsdoc-babel)
### 2.8.4
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.3...2.8.4)
#### Improvements:
* Adds ability to forward errors to express handler (#4697)
* Adds ability to increment the push badge with an arbitrary value (#4889)
* Adds ability to preserve the file names when uploading (#4915)
* `_User` now follow regular ACL policy. Letting administrator lock user out. (#4860) and (#4898)
* Ensure dates are properly handled in aggregates (#4743)
* Aggregates: Improved support for stages sharing the same name
* Add includeAll option
* Added verify password to users router and tests. (#4747)
* Ensure read preference is never overriden, so DB config prevails (#4833)
* add support for geoWithin.centerSphere queries via withJSON (#4825)
* Allow sorting an object field (#4806)
* Postgres: Don't merge JSON fields after save() to keep same behaviour as MongoDB (#4808) (#4815)
#### Dependency updates
* [commander@2.16.0](https://www.npmjs.com/package/commander)
* [mongodb@3.1.1](https://www.npmjs.com/package/mongodb)
* [pg-promise@8.4.5](https://www.npmjs.com/package/pg-promise)
* [ws@6.0.0](https://www.npmjs.com/package/ws)
* [bcrypt@3.0.0](https://www.npmjs.com/package/bcrypt)
* [uws@10.148.1](https://www.npmjs.com/package/uws)
##### Devevelopment Dependencies Updates:
* [cross-env@5.2.0](https://www.npmjs.com/package/cross-env)
* [eslint@5.0.0](https://www.npmjs.com/package/eslint)
* [flow-bin@0.76.0](https://www.npmjs.com/package/flow-bin)
* [mongodb-runner@4.0.0](https://www.npmjs.com/package/mongodb-runner)
* [nodemon@1.18.1](https://www.npmjs.com/package/nodemon)
* [nyc@12.0.2](https://www.npmjs.com/package/nyc)
* [request-promise@4.2.2](https://www.npmjs.com/package/request-promise)
* [supports-color@5.4.0](https://www.npmjs.com/package/supports-color)
### 2.8.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.2...2.8.3)
#### Improvements:
* Adds support for JS SDK 2.0 job status header
* Removes npm-git scripts as npm supports using git repositories that build, thanks to [Florent Vilmart](https://github.com/flovilmart)
### 2.8.2
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.1...2.8.2)
##### Bug Fixes:
* Ensure legacy users without ACL's are not locked out, thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Improvements:
* Use common HTTP agent to increase webhooks performance, thanks to [Tyler Brock](https://github.com/TylerBrock)
* Adds withinPolygon support for Polygon objects, thanks to [Mads Bjerre](https://github.com/madsb)
#### Dependency Updates:
* [ws@5.2.0](https://www.npmjs.com/package/ws)
* [commander@2.15.1](https://www.npmjs.com/package/commander)
* [nodemon@1.17.5](https://www.npmjs.com/package/nodemon)
##### Devevelopment Dependencies Updates:
* [flow-bin@0.73.0](https://www.npmjs.com/package/flow-bin)
* [cross-env@5.1.6](https://www.npmjs.com/package/cross-env)
* [gaze@1.1.3](https://www.npmjs.com/package/gaze)
* [deepcopy@1.0.0](https://www.npmjs.com/package/deepcopy)
* [deep-diff@1.0.1](https://www.npmjs.com/package/deep-diff)
### 2.8.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.1...2.8.0)
Ensure all the files are properly exported to the final package.
### 2.8.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.0...2.7.4)
#### New Features
* Adding Mongodb element to add `arrayMatches` the #4762 (#4766), thanks to [Jérémy Piednoel](https://github.com/jeremypiednoel)
* Adds ability to Lockout users (#4749), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Bug fixes:
* Fixes issue when using afterFind with relations (#4752), thanks to [Florent Vilmart](https://github.com/flovilmart)
* New query condition support to match all strings that starts with some other given strings (#3864), thanks to [Eduard Bosch Bertran](https://github.com/eduardbosch)
* Allow creation of indices on default fields (#4738), thanks to [Claire Neveu](https://github.com/ClaireNeveu)
* Purging empty class (#4676), thanks to [Diamond Lewis](https://github.com/dplewis)
* Postgres: Fixes issues comparing to zero or false (#4667), thanks to [Diamond Lewis](https://github.com/dplewis)
* Fix Aggregate Match Pointer (#4643), thanks to [Diamond Lewis](https://github.com/dplewis)
#### Improvements:
* Allow Parse.Error when returning from Cloud Code (#4695), thanks to [Saulo Tauil](https://github.com/saulogt)
* Fix typo: "requrest" -> "request" (#4761), thanks to [Joseph Frazier](https://github.com/josephfrazier)
* Send version for Vkontakte API (#4725), thanks to [oleg](https://github.com/alekoleg)
* Ensure we respond with invalid password even if email is unverified (#4708), thanks to [dblythy](https://github.com/dblythy)
* Add _password_history to default sensitive data (#4699), thanks to [Jong Eun Lee](https://github.com/yomybaby)
* Check for node version in postinstall script (#4657), thanks to [Diamond Lewis](https://github.com/dplewis)
* Remove FB Graph API version from URL to use the oldest non deprecated version, thanks to [SebC](https://github.com/SebC99)
#### Dependency Updates:
* [@parse/push-adapter@2.0.3](https://www.npmjs.com/package/@parse/push-adapter)
* [@parse/simple-mailgun-adapter@1.0.2](https://www.npmjs.com/package/@parse/simple-mailgun-adapter)
* [uws@10.148.0](https://www.npmjs.com/package/uws)
* [body-parser@1.18.3](https://www.npmjs.com/package/body-parser)
* [mime@2.3.1](https://www.npmjs.com/package/mime)
* [request@2.85.0](https://www.npmjs.com/package/request)
* [mongodb@3.0.7](https://www.npmjs.com/package/mongodb)
* [bcrypt@2.0.1](https://www.npmjs.com/package/bcrypt)
* [ws@5.1.1](https://www.npmjs.com/package/ws)
##### Devevelopment Dependencies Updates:
* [cross-env@5.1.5](https://www.npmjs.com/package/cross-env)
* [flow-bin@0.71.0](https://www.npmjs.com/package/flow-bin)
* [deep-diff@1.0.0](https://www.npmjs.com/package/deep-diff)
* [nodemon@1.17.3](https://www.npmjs.com/package/nodemon)
### 2.7.4
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.4...2.7.3)
#### Bug Fixes:
* Fixes an issue affecting polygon queries, thanks to [Diamond Lewis](https://github.com/dplewis)
#### Dependency Updates:
* [pg-promise@8.2.1](https://www.npmjs.com/package/pg-promise)
##### Development Dependencies Updates:
* [nodemon@1.17.1](https://www.npmjs.com/package/nodemon)
### 2.7.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.3...2.7.2)
@@ -43,7 +355,7 @@
#### Improvements:
* Improved match aggregate
* Do not mark the empty pushs as failed
* Do not mark the empty push as failed
* Support pointer in aggregate query
* Introduces flow types for storage
* Postgres: Refactoring of Postgres Storage Adapter
@@ -1067,7 +1379,7 @@ Other fixes by [Mathias Rangel Wulff](https://github.com/mathiasrw)
* Fix: Improve compatability of cloud code beforeSave hook for newly created object
* Fix: ACL creation for master key only objects
* Fix: Allow uploading files without Content-Type
* Fix: Add features to http requrest to match Parse.com
* Fix: Add features to http request to match Parse.com
* Fix: Bugs in development script when running from locations other than project root
* Fix: Can pass query constraints in URL
* Fix: Objects with legacy "_tombstone" key now don't cause issues.
+1 -1
View File
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at florent@flovilmart.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at codeofconduct@parseplatform.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+61 -12
View File
@@ -1,33 +1,82 @@
### Contributing to Parse Server
# Contributing to Parse Server
#### Pull Requests Welcome!
We really want Parse to be yours, to see it grow and thrive in the open source community.
We really want Parse to be yours, to see it grow and thrive in the open source community.
If you are not familiar with Pull Requests and want to know more about them, you can visit the [Creating a pull request](https://help.github.com/articles/creating-a-pull-request/) article. It contains detailed informations about the process.
##### Please Do's
## Setting up the project for debugging and contributing:
### Recommended setup:
* [vscode](https://code.visualstudio.com), the popular IDE.
* [Jasmine Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-jasmine-test-adapter), a very practical test exploration plugin which let you run, debug and see the test results inline.
### Setting up you local machine:
* [Fork](https://github.com/parse-community/parse-server) this project and clone the fork on your local machine:
```sh
$ git clone https://github.com/parse-community/parse-server
$ cd parse-server # go into the clone directory
$ npm install # install all the node dependencies
$ code . # launch vscode
$ npm run watch # run babel watching for local file changes
```
> To launch VS Code from the terminal with the `code` command you first need to follow the [launching from the command line section](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line) in the VS Code setup documentation.
Once you have babel running in watch mode, you can start making changes to parse-server.
### Good to know:
* The lib/ folder is not commited, so never make changes in there.
* Always make changes to files in the `src/` folder.
* All the tests should point to sources in the `lib/` folder.
### Troubleshooting:
*Question*: I modify the code in the src folder but it doesn't seem to have any effect.<br/>
*Answer*: Check that `npm run watch` is running
*Question*: How do I use breakpoints and debug step by step?<br/>
*Answer*: The easiest way is to install [Jasmine Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer), it will let you run selectively tests and debug them.
*Question*: How do I deploy my forked version on my servers?<br/>
*Answer*: In your `package.json`, update the `parse-server` dependency to `https://github.com/MY_USERNAME/parse-server#MY_FEATURE`. Run `npm install`, commit the changes and deploy to your servers.
### Please Do's
* Begin by reading the [Development Guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) to learn how to get started running the parse-server.
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run:
* **Windows**: `npm run coverage:win`
* **Unix**: `npm run coverage`
* Run the tests for the file you are working on with the following command:
* **Windows**: `npm run test:win spec/MyFile.spec.js`
* **Unix**: `npm test spec/MyFile.spec.js`
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run: `npm run coverage`
* Run the tests for the file you are working on with the following command: `npm test spec/MyFile.spec.js`
* Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at *<PROJECT_ROOT>/coverage/lcov-report/index.html*.
* Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI.
* **Do not** publish the *lib* folder.
##### Run your tests against Postgres (optional)
### Run your tests against Postgres (optional)
If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres.
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres npm test`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/.travis.yml#L37)
* The Postgres adapter has a special debugger that traces all the sql commands. You can enable it with setting the environment variable `PARSE_SERVER_LOG_LEVEL=debug`
* If your feature is intended to only work with MongoDB, you should disable PostgreSQL-specific tests with:
- `describe_only_db('mongo')` // will create a `describe` that runs only on mongoDB
- `it_only_db('mongo')` // will make a test that only runs on mongo
- `it_exclude_dbs(['postgres'])` // will make a test that runs against all DB's but postgres
##### Code of Conduct
### Generate Parse Server Config Definition
If you want to make changes to [Parse Server Configuration][config] add the desired configuration to [src/Options/index.js][config-index] and run `npm run definitions`. This will output [src/Options/Definitions.js][config-def] and [src/Options/docs.js][config-docs].
To view docs run `npm run docs` and check the `/out` directory.
### Code of Conduct
This project adheres to the [Contributor Covenant Code of Conduct](https://github.com/parse-community/parse-server/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to honor this code.
[config]: http://parseplatform.org/parse-server/api/master/ParseServerOptions.html
[config-def]: https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js
[config-docs]: https://github.com/parse-community/parse-server/blob/master/src/Options/docs.js
[config-index]: https://github.com/parse-community/parse-server/blob/master/src/Options/index.js
+26 -11
View File
@@ -1,21 +1,36 @@
FROM node:boron
# Build stage
FROM node:lts-alpine as build
RUN mkdir -p /parse-server
COPY ./ /parse-server/
RUN apk update; \
apk add git;
WORKDIR /tmp
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN mkdir -p /parse-server/config
VOLUME /parse-server/config
# Release stage
FROM node:lts-alpine as release
RUN mkdir -p /parse-server/cloud
VOLUME /parse-server/cloud
RUN apk update; \
apk add git;
VOLUME /parse-server/cloud /parse-server/config
WORKDIR /parse-server
RUN npm install && \
npm run build
COPY package*.json ./
RUN npm ci --production --ignore-scripts
COPY bin bin
COPY public_html public_html
COPY views views
COPY --from=build /tmp/lib lib
RUN mkdir -p logs && chown -R node: logs
ENV PORT=1337
USER node
EXPOSE $PORT
ENTRYPOINT ["npm", "start", "--"]
ENTRYPOINT ["node", "./bin/parse-server"]
+367 -124
View File
@@ -1,16 +1,24 @@
![Parse Server logo](.github/parse-server-logo.png?raw=true)
![Parse Server logo](https://github.com/parse-community/parse-server/raw/master/.github/parse-server-logo.png)
[![Backers on Open Collective](https://opencollective.com/parse-server/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/parse-server/sponsors/badge.svg)](#sponsors)
[![Build Status](https://img.shields.io/travis/parse-community/parse-server/master.svg?style=flat)](https://travis-ci.org/parse-community/parse-server)
[![Coverage Status](https://img.shields.io/codecov/c/github/parse-community/parse-server/master.svg)](https://codecov.io/github/parse-community/parse-server?branch=master)
[![npm version](https://img.shields.io/npm/v/parse-server.svg?style=flat)](https://www.npmjs.com/package/parse-server)
[![Join Chat](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/ParsePlatform/Chat)
[![Join The Conversation](https://img.shields.io/discourse/https/community.parseplatform.org/topics.svg)](https://community.parseplatform.org/c/parse-server)
[![Greenkeeper badge](https://badges.greenkeeper.io/parse-community/parse-server.svg)](https://greenkeeper.io/)
[![Twitter Follow](https://img.shields.io/twitter/follow/ParsePlatform.svg?label=Follow%20us%20on%20Twitter&style=social)](https://twitter.com/intent/follow?screen_name=ParsePlatform)
[![](https://img.shields.io/badge/mongodb-3.2-green.svg?logo=mongodb&style=flat)]()
[![](https://img.shields.io/badge/mongodb-3.4-green.svg?logo=mongodb&style=flat)]()
[![](https://img.shields.io/badge/mongodb-3.6-green.svg?logo=mongodb&style=flat)]()
[![](https://img.shields.io/badge/mongodb-4.0-green.svg?logo=mongodb&style=flat)]()
Parse Server is an [open source version of the Parse backend](http://blog.parseplatform.org/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.
The full documentation for Parse Server is available in the [wiki](https://github.com/parse-community/parse-server/wiki). The [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/) is a good place to get started. An [API reference](http://parseplatform.org/parse-server/api/) is also available. If you're interested in developing for Parse Server, the [Development guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) will help you get set up.
- [Getting Started](#getting-started)
- [Running Parse Server](#running-parse-server)
- [Locally](#locally)
@@ -20,27 +28,28 @@ Parse Server works with the Express web application framework. It can be added t
- [Running elsewhere](#running-parse-server-elsewhere)
- [Sample Application](#parse-server-sample-application)
- [Parse Server + Express](#parse-server--express)
- [Logging](#logging)
- [Documentation](#documentation)
- [Configuration](#configuration)
- [Basic Options](#basic-options)
- [Client Key Options](#client-key-options)
- [Advanced Options](#advanced-options)
- [Logging](#logging-1)
- [Email Verification & Password Reset](#email-verification-and-password-reset)
- [Email Verification & Password Reset](#email-verification-and-password-reset)
- [Custom Pages](#custom-pages)
- [Using Environment Variables](#using-environment-variables-to-configure-parse-server)
- [Available Adapters](#available-adapters)
- [Configuring File Adapters](#configuring-file-adapters)
- [Logging](#logging)
- [Live Queries](#live-queries)
- [GraphQL](#graphql)
- [Upgrading to 3.0.0](#upgrading-to-300)
- [Support](#support)
- [Ride the Bleeding Edge](#want-to-ride-the-bleeding-edge)
- [Contributing](#contributing)
- [Backers](#backers)
- [Contributors](#contributors)
- [Sponsors](#sponsors)
- [Backers](#backers)
# Getting Started
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parseplatform.org/learn/parse-server-video-series-april-2016/](http://blog.parseplatform.org/learn/parse-server-video-series-april-2016/)
The fastest and easiest way to get started is to run MongoDB and Parse Server locally.
## Running Parse Server
@@ -52,14 +61,16 @@ $ mongodb-runner start
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test
```
***Note:*** *If installation with* `-g` *fails due to permission problems* (`npm ERR! code 'EACCES'`), *please refer to [this link](https://docs.npmjs.com/getting-started/fixing-npm-permissions).*
### Inside a Docker container
```
$ git clone https://github.com/parse-community/parse-server
$ cd parse-server
$ docker build --tag parse-server .
$ docker run --name my-mongo -d mongo
$ docker run --name my-parse-server --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test
```
```
You can use any arbitrary string as your application id and master key. These will be used by your clients to authenticate with the Parse Server.
@@ -135,7 +146,7 @@ To learn more about using saving and querying objects on Parse Server, check out
### Connect your app to Parse Server
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://github.com/parse-community/parse-server/wiki/Parse-Server-Guide#using-parse-sdks-with-parse-server).
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://docs.parseplatform.org/parse-server/guide/#using-parse-sdks-with-parse-server).
## Running Parse Server elsewhere
@@ -153,7 +164,7 @@ We have provided a basic [Node.js application](https://github.com/parse-communit
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
* [Pivotal Web Services](https://github.com/cf-platform-eng/pws-parse-server)
* [Back4app](http://blog.back4app.com/2016/03/01/quick-wizard-migration/)
* [Gomix](https://gomix.com/#!/project/parse-server)
* [Glitch](https://glitch.com/edit/#!/parse-server)
* [Flynn](https://flynn.io/blog/parse-apps-on-flynn)
### Parse Server + Express
@@ -182,35 +193,15 @@ app.listen(1337, function() {
});
```
For a full list of available options, run `parse-server --help`.
## Logging
Parse Server will, by default, log:
* to the console
* daily rotating files as new line delimited JSON
Logs are also be viewable in Parse Dashboard.
**Want to log each request and response?** Set the `VERBOSE` environment variable when starting `parse-server`. Usage :- `VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
**Want logs to be in placed in other folder?** Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
**Want to log specific levels?** Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL`
**Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc.)?** Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
# Documentation
The full documentation for Parse Server is available in the [wiki](https://github.com/parse-community/parse-server/wiki). The [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/) is a good place to get started. If you're interested in developing for Parse Server, the [Development guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) will help you get set up.
For a full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).
## Configuration
Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options.
For the full list of available options, run `parse-server --help`.
For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).
#### Basic options
### Basic options
* `appId` **(required)** - The application id to host with this server instance. You can use any arbitrary string. For migrated apps, this should match your hosted Parse app.
* `masterKey` **(required)** - The master key to use for overriding ACL security. You can use any arbitrary string. Keep it secret! For migrated apps, this should match your hosted Parse app.
@@ -220,7 +211,7 @@ For the full list of available options, run `parse-server --help`.
* `cloud` - The absolute path to your cloud code `main.js` file.
* `push` - Configuration options for APNS and GCM push. See the [Push Notifications quick start](http://docs.parseplatform.org/parse-server/guide/#push-notifications_push-notifications-quick-start).
#### Client key options
### Client key options
The client keys used with Parse are no longer necessary with Parse Server. If you wish to still require them, perhaps to be able to refuse access to older clients, you can set the keys at initialization time. Setting any of these keys will require all requests to provide one of the configured keys.
@@ -229,40 +220,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
* `restAPIKey`
* `dotNetKey`
#### Advanced options
* `fileKey` - For migrated apps, this is necessary to provide access to files already hosted on Parse.
* `allowClientClassCreation` - Set to false to disable client class creation. Defaults to true.
* `enableAnonymousUsers` - Set to false to disable anonymous users. Defaults to true.
* `auth` - Used to configure support for [3rd party authentication](http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).
* `facebookAppIds` - An array of valid Facebook application IDs that users may authenticate with.
* `mountPath` - Mount path for the server. Defaults to `/parse`.
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
* `logLevel` - Set the specific level you want to log. Defaults to `info`. The default logger uses the npm log levels as defined by the underlying winston logger. Check [Winston logging levels](https://github.com/winstonjs/winston#logging-levels) for details on values to specify.
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
* `maxLimit` - The maximum value supported for the limit option on queries. Defaults to unlimited.
* `revokeSessionOnPasswordReset` - When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
* `passwordPolicy` - Optional password policy rules to enforce.
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
* `middleware` - (CLI only), a module name, function that is an express middleware. When using the CLI, the express app will load it just **before** mounting parse-server on the mount path. This option is useful for injecting a monitoring middleware.
* `masterKeyIps` - The array of ip addresses where masterKey usage will be restricted to only these ips. (Default to [] which means allow all ips). If you're using this feature and have `useMasterKey: true` in cloudcode, make sure that you put your own ip in this list.
* `readOnlyMasterKey` - A masterKey that has full read access to the data, but no write access. This key should be treated the same way as your masterKey, keeping it private.
* `objectIdSize` - The string length of the newly generated object's ids.
##### Logging
Use the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server` to save your server logfiles to the specified folder.
Usage:
```
PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
```
##### Email verification and password reset
### Email verification and password reset
Verifying user email addresses and enabling password reset via email requires an email adapter. As part of the `parse-server` package we provide an adapter for sending email through Mailgun. To use it, sign up for Mailgun, and add this to your initialization code:
@@ -314,14 +272,15 @@ var server = ParseServer({
},
// optional settings to enforce password policies
passwordPolicy: {
// Two optional settings to enforce strong passwords. Either one or both can be specified.
// Two optional settings to enforce strong passwords. Either one or both can be specified.
// If both are specified, both checks must pass to accept the password
// 1. a RegExp object or a regex string representing the pattern to enforce
// 1. a RegExp object or a regex string representing the pattern to enforce
validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit
// 2. a callback function to be invoked to validate the password
validatorCallback: (password) => { return validatePassword(password) },
// 2. a callback function to be invoked to validate the password
validatorCallback: (password) => { return validatePassword(password) },
validationError: 'Password must contain at least 1 digit.' // optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message.
doNotAllowUsername: true, // optional setting to disallow username in passwords
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
maxPasswordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history.
//optional setting to set a validity duration for password reset links (in seconds)
resetTokenValidityDuration: 24*60*60, // expire after 24 hours
@@ -330,6 +289,7 @@ var server = ParseServer({
```
You can also use other email adapters contributed by the community such as:
- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template)
- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter)
- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter)
- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter)
@@ -340,6 +300,27 @@ You can also use other email adapters contributed by the community such as:
- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter)
- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter)
### Custom Pages
It’s possible to change the default pages of the app and redirect the user to another path or domain.
```js
var server = ParseServer({
...otherOptions,
customPages {
passwordResetSuccess: "http://yourapp.com/passwordResetSuccess",
verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess",
parseFrameURL: "http://yourapp.com/parseFrameURL",
linkSendSuccess: "http://yourapp.com/linkSendSuccess",
linkSendFail: "http://yourapp.com/linkSendFail",
invalidLink: "http://yourapp.com/invalidLink",
invalidVerificationLink: "http://yourapp.com/invalidVerificationLink",
choosePassword: "http://yourapp.com/choosePassword"
}
})
```
### Using environment variables to configure Parse Server
You may configure the Parse Server using environment variables:
@@ -359,7 +340,7 @@ The default port is 1337, to use a different port set the PORT environment varia
$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
```
For the full list of configurable environment variables, run `parse-server --help`.
For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js).
### Available Adapters
@@ -373,15 +354,295 @@ You can also find more adapters maintained by the community by searching on [npm
Parse Server allows developers to choose from several options when hosting files:
* `GridStoreAdapter`, which is backed by MongoDB;
* `GridFSBucketAdapter`, which is backed by MongoDB;
* `S3Adapter`, which is backed by [Amazon S3](https://aws.amazon.com/s3/); or
* `GCSAdapter`, which is backed by [Google Cloud Storage](https://cloud.google.com/storage/)
`GridStoreAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters).
`GridFSBucketAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters).
### Logging
Parse Server will, by default, log:
* to the console
* daily rotating files as new line delimited JSON
Logs are also viewable in Parse Dashboard.
**Want to log each request and response?** Set the `VERBOSE` environment variable when starting `parse-server`. Usage :- `VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
**Want logs to be in placed in a different folder?** Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
**Want to log specific levels?** Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL`
**Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc)?** Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
# Live Queries
Live queries are meant to be used in real-time reactive applications, where just using the traditional query paradigm could cause several problems, like increased response time and high network and server usage. Live queries should be used in cases where you need to continuously update a page with fresh data coming from the database, which often happens in (but is not limited to) online games, messaging clients and shared to-do lists.
Take a look at [Live Query Guide](https://docs.parseplatform.org/parse-server/guide/#live-queries), [Live Query Server Setup Guide](https://docs.parseplatform.org/parse-server/guide/#scalability) and [Live Query Protocol Specification](https://github.com/parse-community/parse-server/wiki/Parse-LiveQuery-Protocol-Specification). You can setup a standalone server or multiple instances for scalability (recommended).
# GraphQL
[GraphQL](https://graphql.org/), developed by Facebook, is an open-source data query and manipulation language for APIs. In addition to the traditional REST API, Parse Server automatically generates a GraphQL API based on your current application schema. Parse Server also allows you to define your custom GraphQL queries and mutations, whose resolvers can be bound to your cloud code functions.
## Running
### Using the CLI
The easiest way to run the Parse GraphQL API is through the CLI:
```
$ npm install -g parse-server mongodb-runner
$ mongodb-runner start
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --mountGraphQL --mountPlayground
```
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
***Note:*** Do ***NOT*** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps.
### Using Docker
You can also run the Parse GraphQL API inside a Docker container:
```
$ git clone https://github.com/parse-community/parse-server
$ cd parse-server
$ docker build --tag parse-server .
$ docker run --name my-mongo -d mongo
$ docker run --name my-parse-server --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test --mountGraphQL --mountPlayground
```
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
***Note:*** Do ***NOT*** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps.
### Using Express.js
You can also mount the GraphQL API in an Express.js application together with the REST API or solo:
```
const express = require('express');
const { default: ParseServer, ParseGraphQLServer } = require('parse-server');
const app = express();
const parseServer = new ParseServer({
databaseURI: 'mongodb://localhost:27017/test',
appId: 'APPLICATION_ID',
masterKey: 'MASTER_KEY',
serverURL: 'http://localhost:1337/parse'
});
const parseGraphQLServer = new ParseGraphQLServer(
parseServer,
{
graphQLPath: '/graphql',
playgroundPath: '/playground'
}
);
app.use('/parse', parseServer.app); // (Optional) Mounts the REST API
parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production
app.listen(1337, function() {
console.log('REST API running on http://localhost:1337/parse');
console.log('GraphQL API running on http://localhost:1337/graphql');
console.log('GraphQL Playground running on http://localhost:1337/playground');
});
```
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
***Note:*** Do ***NOT*** mount the GraphQL Playground in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps.
## Checking the API health
Run the following:
```graphql
query Health {
health
}
```
You should receive the following response:
```json
{
"data": {
"health": true
}
}
```
## Creating your first object
Since your application does not have a schema yet, you can use the generic `create` mutation to create your first object. Run the following:
```graphql
mutation CreateObject {
objects {
create(className: "GameScore" fields: { score: 1337 playerName: "Sean Plott" cheatMode: false }) {
objectId
createdAt
}
}
}
```
You should receive a response similar to this:
```json
{
"data": {
"objects": {
"create": {
"objectId": "7jfBmbGgyF",
"createdAt": "2019-06-20T23:50:50.825Z"
}
}
}
}
```
## Using automatically generated operations
Parse Server learned from the first object that you created and now you have the `GameScore` class in your schema. You can now start using the automatically generated operations!
Run the following to create a second object:
```graphql
mutation CreateGameScore {
objects {
createGameScore(fields: { score: 2558 playerName: "Luke Skywalker" cheatMode: false }) {
objectId
createdAt
}
}
}
```
You should receive a response similar to this:
```json
{
"data": {
"objects": {
"createGameScore": {
"objectId": "gySYolb2CL",
"createdAt": "2019-06-20T23:56:37.114Z"
}
}
}
}
```
You can also run a query to this new class:
```graphql
query FindGameScore {
objects {
findGameScore {
results {
playerName
score
}
}
}
}
```
You should receive a response similar to this:
```json
{
"data": {
"objects": {
"findGameScore": {
"results": [
{
"playerName": "Sean Plott",
"score": 1337
},
{
"playerName": "Luke Skywalker",
"score": 2558
}
]
}
}
}
}
```
## Customizing your GraphQL Schema
Parse GraphQL Server allows you to create a custom GraphQL schema with own queries and mutations to be merged with the auto-generated ones. You can resolve these operations using your regular cloud code functions.
To start creating your custom schema, you need to code a `schema.graphql` file and initialize Parse Server with `--graphQLSchema` and `--cloud` options:
```bash
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --mountGraphQL --mountPlayground --graphQLSchema ./schema.graphql --cloud ./main.js
```
### Creating your first custom query
Use the code below for your `schema.graphql` and `main.js` files. Then restart your Parse Server.
```graphql
# schema.graphql
extend type Query {
hello: String! @resolve
}
```
```js
// main.js
Parse.Cloud.define('hello', async () => {
return 'Hello world!';
});
```
You can now run your custom query using GraphQL Playground:
```graphql
query {
hello
}
```
You should receive the response below:
```json
{
"data": {
"hello": "Hello world!"
}
}
```
## Learning more
The [Parse GraphQL Guide](http://docs.parseplatform.org/graphql/guide/) is a very good source for learning how to use the Parse GraphQL API.
You also have a very powerful tool inside your GraphQL Playground. Please look at the right side of your GraphQL Playground. You will see the `DOCS` and `SCHEMA` menus. They are automatically generated by analyzing your application schema. Please refer to them and learn more about everything that you can do with your Parse GraphQL API.
Additionally, the [GraphQL Learn Section](https://graphql.org/learn/) is a very good source to learn more about the power of the GraphQL language.
# Upgrading to 3.0.0
Starting 3.0.0, parse-server uses the JS SDK version 2.0.
In short, parse SDK v2.0 removes the backbone style callbacks as well as the Parse.Promise object in favor of native promises.
All the Cloud Code interfaces also have been updated to reflect those changes, and all backbone style response objects are removed and replaced by Promise style resolution.
We have written up a [migration guide](3.0.0.md), hoping this will help you transition to the next major release.
# Support
For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities.
Please take a look at our [support document](https://github.com/parse-community/.github/blob/master/SUPPORT.md).
If you believe you've found an issue with Parse Server, make sure these boxes are checked before [reporting an issue](https://github.com/parse-community/parse-server/issues):
@@ -393,64 +654,37 @@ If you believe you've found an issue with Parse Server, make sure these boxes ar
# Want to ride the bleeding edge?
The `latest` branch in this repository is automatically maintained to be the last
commit to `master` to pass all tests, in the same form found on npm. It is
recommend to use builds deployed npm for many reasons, but if you want to use
It is recommend to use builds deployed npm for many reasons, but if you want to use
the latest not-yet-released version of parse-server, you can do so by depending
directly on this branch:
```
npm install parseplatform/parse-server.git#latest
npm install parse-community/parse-server.git#master
```
## Experimenting
You can also use your own forks, and work in progress branches by specifying them:
```
npm install github:myUsername/parse-server#my-awesome-feature
```
And don't forget, if you plan to deploy it remotely, you should run `npm install` with the `--save` option.
# Contributing
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
-----
# Contributors
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
# Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/parse-server#backer)]
<a href="https://opencollective.com/parse-server/backer/0/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/1/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/2/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/3/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/4/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/5/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/6/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/7/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/8/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/9/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/10/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/11/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/12/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/13/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/14/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/15/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/16/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/17/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/18/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/19/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/20/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/21/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/22/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/23/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/24/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/25/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/26/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/27/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/28/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/29/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/29/avatar.svg"></a>
This project exists thanks to all the people who contribute... we'd love to see your face on this list!
<a href="../../graphs/contributors"><img src="https://opencollective.com/parse-server/contributors.svg?width=890&button=false" /></a>
# Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/parse-server#sponsor)]
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor!](https://opencollective.com/parse-server#sponsor)
<a href="https://opencollective.com/parse-server/sponsor/0/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/1/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/1/avatar.svg"></a>
@@ -483,3 +717,12 @@ Become a sponsor and get your logo on our README on Github with a link to your s
<a href="https://opencollective.com/parse-server/sponsor/28/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/29/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/29/avatar.svg"></a>
# Backers
Support us with a monthly donation and help us continue our activities. [Become a backer!](https://opencollective.com/parse-server#backer)
<a href="https://opencollective.com/parse-server#backers" target="_blank"><img src="https://opencollective.com/parse-server/backers.svg?width=890" /></a>
-----
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
+2 -2
View File
@@ -2,7 +2,7 @@
If you believe you have found a security vulnerability on one of parse-community maintained packages,
we encourage you to let us know right away.
We will investigate all legitimate reports and do our best to quickly fix the problem.
Before reporting though, please review this page including and those things that should not be reported.
Before making a report, please review this page to understand our disclosure policy and how to communicate with us.
# Responsible Disclosure Policy
If you comply with the policies below when reporting a security issue to parse community,
@@ -16,7 +16,7 @@ We ask that:
# Communicating with us
All vulnerability should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
All vulnerabilities should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
You can use our PGP public key, which is also uploaded [here](hkp://pgp.mit.edu):
-37
View File
@@ -1,37 +0,0 @@
#!/usr/bin/env node
var nodemon = require('nodemon');
var babel = require("babel-core");
var gaze = require('gaze');
var fs = require('fs');
var path = require('path');
// Watch the src and transpile when changed
gaze('src/**/*', function(err, watcher) {
if (err) throw err;
watcher.on('changed', function(sourceFile) {
console.log(sourceFile + " has changed");
try {
targetFile = path.relative(__dirname, sourceFile).replace(/\/src\//, '/lib/');
targetFile = path.resolve(__dirname, targetFile);
fs.writeFile(targetFile, babel.transformFileSync(sourceFile).code);
} catch (e) {
console.error(e.message, e.stack);
}
});
});
try {
// Run and watch dist
nodemon({
script: 'bin/parse-server',
ext: 'js json',
watch: 'lib'
});
} catch (e) {
console.error(e.message, e.stack);
}
process.once('SIGINT', function() {
process.exit(0);
});
+23
View File
@@ -0,0 +1,23 @@
{
"plugins": ["node_modules/jsdoc-babel", "plugins/markdown"],
"babel": {
"plugins": ["@babel/plugin-transform-flow-strip-types"]
},
"source": {
"include": ["./README.md", "./src/cloud-code", "./src/Options/docs.js", "./src/ParseServer.js", "./src/Adapters"],
"excludePattern": "(^|\\/|\\\\)_"
},
"templates": {
"default": {
"outputSourceFiles": false,
"showInheritedInNav": false,
"useLongnameInNav": true
},
"cleverLinks": true,
"monospaceLinks": false
},
"opts": {
"template": "node_modules/@parse/minami",
"recurse": true
}
}
+11416
View File
File diff suppressed because it is too large Load Diff
+84 -47
View File
@@ -1,11 +1,11 @@
{
"name": "parse-server",
"version": "2.7.3",
"version": "3.7.0",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/ParsePlatform/parse-server"
"url": "https://github.com/parse-community/parse-server"
},
"files": [
"bin/",
@@ -14,80 +14,117 @@
"views/",
"LICENSE",
"PATENTS",
"postinstall.js",
"README.md"
],
"license": "BSD-3-Clause",
"dependencies": {
"@apollographql/graphql-playground-html": "1.6.24",
"@parse/fs-files-adapter": "1.0.1",
"@parse/push-adapter": "2.0.2",
"@parse/s3-files-adapter": "1.2.1",
"@parse/simple-mailgun-adapter": "1.0.1",
"@parse/push-adapter": "3.0.8",
"@parse/s3-files-adapter": "1.2.3",
"@parse/simple-mailgun-adapter": "1.1.0",
"apollo-server-express": "2.7.2",
"bcryptjs": "2.4.3",
"body-parser": "1.18.2",
"commander": "2.15.0",
"deepcopy": "0.6.3",
"express": "4.16.2",
"body-parser": "1.19.0",
"commander": "2.20.0",
"cors": "2.8.5",
"deepcopy": "2.0.0",
"express": "4.17.1",
"follow-redirects": "1.7.0",
"graphql": "14.4.2",
"graphql-list-fields": "2.0.2",
"graphql-tools": "^4.0.5",
"graphql-upload": "8.0.7",
"intersect": "1.0.1",
"lodash": "4.17.5",
"lru-cache": "4.1.2",
"mime": "2.2.0",
"mongodb": "3.0.4",
"multer": "1.3.0",
"parse": "1.11.1",
"pg-promise": "8.2.0",
"jsonwebtoken": "8.5.1",
"lodash": "4.17.15",
"lru-cache": "5.1.1",
"mime": "2.4.4",
"mongodb": "3.2.7",
"node-rsa": "1.0.5",
"parse": "2.6.0",
"pg-promise": "9.0.0",
"redis": "2.8.0",
"request": "2.83.0",
"semver": "5.5.0",
"semver": "6.3.0",
"subscriptions-transport-ws": "0.9.16",
"tv4": "1.3.0",
"uuid": "^3.1.0",
"winston": "2.4.1",
"winston-daily-rotate-file": "1.7.2",
"ws": "5.0.0"
"uuid": "3.3.2",
"winston": "3.2.1",
"winston-daily-rotate-file": "3.10.0",
"ws": "7.1.1"
},
"devDependencies": {
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-eslint": "^8.2.2",
"babel-plugin-transform-flow-strip-types": "6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "1.6.1",
"@babel/cli": "7.5.5",
"@babel/core": "7.5.5",
"@babel/plugin-proposal-object-rest-spread": "7.5.5",
"@babel/plugin-transform-flow-strip-types": "7.4.4",
"@babel/preset-env": "7.5.5",
"@parse/minami": "1.0.0",
"apollo-cache-inmemory": "1.6.2",
"apollo-client": "2.6.3",
"apollo-link": "1.2.12",
"apollo-link-http": "1.5.15",
"apollo-link-ws": "1.0.18",
"apollo-upload-client": "11.0.0",
"apollo-utilities": "1.3.2",
"babel-eslint": "10.0.2",
"bcrypt-nodejs": "0.0.3",
"cross-env": "5.1.4",
"deep-diff": "0.3.8",
"eslint": "^4.9.0",
"eslint-plugin-flowtype": "^2.39.1",
"flow-bin": "^0.67.1",
"gaze": "1.1.2",
"jasmine": "3.1.0",
"jasmine-spec-reporter": "^4.1.0",
"mongodb-runner": "3.6.1",
"nodemon": "1.15.0",
"nyc": "^11.0.2",
"request-promise": "4.2.2"
"cross-env": "5.2.0",
"deep-diff": "1.0.2",
"eslint": "6.1.0",
"eslint-plugin-flowtype": "3.12.2",
"flow-bin": "0.104.0",
"form-data": "2.5.0",
"gaze": "1.1.3",
"graphql-tag": "^2.10.1",
"husky": "3.0.1",
"jasmine": "3.4.0",
"jsdoc": "3.6.3",
"jsdoc-babel": "0.5.0",
"lint-staged": "9.2.1",
"mongodb-runner": "4.3.2",
"node-fetch": "2.6.0",
"nyc": "14.1.1",
"prettier": "1.18.2"
},
"scripts": {
"definitions": "node ./resources/buildConfigDefinitions.js",
"docs": "jsdoc -c ./jsdoc-conf.json",
"dev": "npm run build && node bin/dev",
"lint": "flow && eslint --cache ./",
"build": "babel src/ -d lib/ --copy-files",
"pretest": "npm run lint",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
"watch": "babel --watch src/ -d lib/ --copy-files",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
"start": "node ./bin/parse-server",
"prepublish": "npm run build"
"prepare": "npm run build",
"postinstall": "node -p 'require(\"./postinstall.js\")()'"
},
"engines": {
"node": ">=6.11.4"
"node": ">= 8"
},
"bin": {
"parse-server": "./bin/parse-server"
},
"optionalDependencies": {
"bcrypt": "1.0.3",
"uws": "^9.14.0"
"bcrypt": "3.0.6"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/parse-server",
"logo": "https://opencollective.com/parse-server/logo.txt?reverse=true&variant=binary"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"{src,spec}/**/*.js": [
"prettier --write",
"eslint --cache",
"git add"
]
}
}
+50
View File
@@ -0,0 +1,50 @@
const pkg = require('./package.json');
const version = parseFloat(process.version.substr(1));
const minimum = parseFloat(pkg.engines.node.match(/\d+/g).join('.'));
module.exports = function () {
const openCollective = `
1111111111
1111111111111111
1111111111111111111111
11111111111111111111111111
111111111111111 11111111
1111111111111 111111
1111111111111 111111111 111111
111111111111 11111111111 111111
1111111111111 11111111111 111111
1111111111111 1111111111 111111
1111111111111111111111111 1111111
11111111 11111111
111111 1111111111111111111
11111 11111 111111111111111111
11111 11111111111111111
111111 111111111111111111
11111111111111111111111111
1111111111111111111111
111111111111111111
11111111111
Thanks for installing parse 🙏
Please consider donating to our open collective
to help us maintain this package.
👉 https://opencollective.com/parse-server
`;
process.stdout.write(openCollective);
if (version >= minimum) {
process.exit(0);
}
const errorMessage = `
⚠️ parse-server requires at least node@${minimum}!
You have node@${version}
`;
process.stdout.write(errorMessage);
process.exit(1);
};
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh -e
set -x
if [ "${TRAVIS_REPO_SLUG}" = "" ];
then
echo "Cannot release docs without TRAVIS_REPO_SLUG set"
exit 0;
fi
REPO="https://github.com/${TRAVIS_REPO_SLUG}"
rm -rf docs
git clone -b gh-pages --single-branch $REPO ./docs
cd docs
git pull origin gh-pages
cd ..
DEST="master"
if [ "${TRAVIS_TAG}" != "" ];
then
DEST="${TRAVIS_TAG}"
# change the default page to the latest
echo "<meta http-equiv='refresh' content='0; url=/parse-server/api/${DEST}'>" > "docs/api/index.html"
fi
npm run definitions
npm run docs
mkdir -p "docs/api/${DEST}"
cp -R out/* "docs/api/${DEST}"
+67 -24
View File
@@ -56,23 +56,35 @@ function getENVPrefix(iface) {
function processProperty(property, iface) {
const firstComment = getCommentValue(last(property.leadingComments || []));
const lastComment = getCommentValue((property.trailingComments || [])[0]);
const name = property.key.name;
const prefix = getENVPrefix(iface);
if (!firstComment) {
return;
}
const components = firstComment.split(':ENV:').map((elt) => {
return elt.trim();
const lines = firstComment.split('\n').map((line) => line.trim());
let help = '';
let envLine;
let defaultLine;
lines.forEach((line) => {
if (line.indexOf(':ENV:') === 0) {
envLine = line;
} else if (line.indexOf(':DEFAULT:') === 0) {
defaultLine = line;
} else {
help += line;
}
});
let env;
if (envLine) {
env = envLine.split(' ')[1];
} else {
env = (prefix + toENV(name));
}
let defaultValue;
if (lastComment && lastComment.indexOf('=') >= 0) {
const slice = lastComment.slice(lastComment.indexOf('=') + 1, lastComment.length).trim();
defaultValue = slice;
if (defaultLine) {
defaultValue = defaultLine.split(' ')[1];
}
const help = components[0];
const env = components[1] || (prefix + toENV(name));
let type = property.value.type;
let isRequired = true;
if (type == 'NullableTypeAnnotation') {
@@ -94,6 +106,7 @@ function processProperty(property, iface) {
function doInterface(iface) {
return iface.body.properties
.sort((a, b) => a.key.name.localeCompare(b.key.name))
.map((prop) => processProperty(prop, iface))
.filter((e) => e !== undefined);
}
@@ -123,18 +136,18 @@ function mapperFor(elt, t) {
}
function parseDefaultValue(elt, value, t) {
let litteralValue;
let literalValue;
if (t.isStringTypeAnnotation(elt)) {
if (value == '""' || value == "''") {
litteralValue = t.stringLiteral('');
literalValue = t.stringLiteral('');
} else {
litteralValue = t.stringLiteral(value);
literalValue = t.stringLiteral(value);
}
} else if (t.isNumberTypeAnnotation(elt)) {
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
} else if (t.isArrayTypeAnnotation(elt)) {
const array = parsers.objectParser(value);
litteralValue = t.arrayExpression(array.map((value) => {
literalValue = t.arrayExpression(array.map((value) => {
if (typeof value == 'string') {
return t.stringLiteral(value);
} else {
@@ -142,27 +155,36 @@ function parseDefaultValue(elt, value, t) {
}
}));
} else if (t.isAnyTypeAnnotation(elt)) {
litteralValue = t.arrayExpression([]);
literalValue = t.arrayExpression([]);
} else if (t.isBooleanTypeAnnotation(elt)) {
litteralValue = t.booleanLiteral(parsers.booleanParser(value));
literalValue = t.booleanLiteral(parsers.booleanParser(value));
} else if (t.isGenericTypeAnnotation(elt)) {
const type = elt.typeAnnotation.id.name;
if (type == 'NumberOrBoolean') {
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
}
if (type == 'CustomPagesOptions') {
const object = parsers.objectParser(value);
const props = Object.keys(object).map((key) => {
return t.objectProperty(key, object[value]);
});
litteralValue = t.objectExpression(props);
literalValue = t.objectExpression(props);
}
if (type == 'ProtectedFields') {
const prop = t.objectProperty(
t.stringLiteral('_User'), t.objectPattern([
t.objectProperty(t.stringLiteral('*'), t.arrayExpression([t.stringLiteral('email')]))
])
);
literalValue = t.objectExpression([prop]);
}
}
return litteralValue;
return literalValue;
}
function inject(t, list) {
return list.map((elt) => {
let comments = '';
const results = list.map((elt) => {
if (!elt.name) {
return;
}
@@ -186,23 +208,42 @@ function inject(t, list) {
throw new Error(`Unable to parse value for ${elt.name} `);
}
}
let type = elt.type.replace('TypeAnnotation', '');
if (type === 'Generic') {
type = elt.typeAnnotation.id.name;
}
if (type === 'Array') {
type = `${elt.typeAnnotation.elementType.type.replace('TypeAnnotation', '')}[]`;
}
if (type === 'NumberOrBoolean') {
type = 'Number|Boolean';
}
if (type === 'Adapter') {
const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name;
type = `Adapter<${adapterType}>`;
}
comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`;
const obj = t.objectExpression(props);
return t.objectProperty(t.stringLiteral(elt.name), obj);
}).filter((elt) => {
return elt != undefined;
});
return { results, comments };
}
const makeRequire = function(variableName, module, t) {
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
return t.variableDeclaration('var', [decl])
}
let docs = ``;
const plugin = function (babel) {
const t = babel.types;
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
return {
visitor: {
ImportDeclaration: function(path) {
path.remove();
},
Program: function(path) {
// Inject the parser's loader
path.unshiftContainer('body', makeRequire('parsers', './parsers', t));
@@ -210,11 +251,12 @@ const plugin = function (babel) {
ExportDeclaration: function(path) {
// Export declaration on an interface
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
const l = inject(t, doInterface(path.node.declaration));
const { results, comments } = inject(t, doInterface(path.node.declaration));
const id = path.node.declaration.id.name;
const exports = t.memberExpression(moduleExports, t.identifier(id));
docs += `/**\n * @interface ${id}\n${comments} */\n\n`;
path.replaceWith(
t.assignmentExpression('=', exports, t.objectExpression(l))
t.assignmentExpression('=', exports, t.objectExpression(results))
)
}
}
@@ -228,6 +270,7 @@ This code has been generated by resources/buildConfigDefinitions.js
Do not edit manually, but update Options/index.js
`
const babel = require("babel-core");
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin ], auxiliaryCommentBefore });
const babel = require("@babel/core");
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin, '@babel/transform-flow-strip-types' ], babelrc: false, auxiliaryCommentBefore, sourceMaps: false });
require('fs').writeFileSync('./src/Options/Definitions.js', res.code + '\n');
require('fs').writeFileSync('./src/Options/docs.js', docs);
-39
View File
@@ -1,39 +0,0 @@
#!/bin/sh -e
# This script maintains a git branch which mirrors master but in a form that
# what will eventually be deployed to npm, allowing npm dependencies to use:
#
# "parse-server": "parseplatform/parse-server#latest"
#
# From: https://github.com/graphql/graphql-js/blob/master/resources/npm-git.sh
BUILD_DIR=latest
BRANCH="${TRAVIS_BRANCH}"
TARGET="latest"
if [ "$BRANCH" != "master" ];
then
TARGET="$BRANCH-preview"
fi
npm run build
mkdir -p $BUILD_DIR
cp package.json $BUILD_DIR/
cp README.md $BUILD_DIR/
cp LICENSE $BUILD_DIR/
cp PATENTS $BUILD_DIR/
cp CHANGELOG.md $BUILD_DIR/
cp -R lib $BUILD_DIR
cp -R bin $BUILD_DIR
cp -R public_html $BUILD_DIR
cp -R views $BUILD_DIR
cd $BUILD_DIR
git init
git config user.name "Travis CI"
git config user.email "github@fb.com"
git add .
git commit -m "Deploy $BRANCH to $TARGET branch"
git push --force --quiet "https://${GH_TOKEN}@github.com/parse-community/parse-server.git" master:$TARGET
+14
View File
@@ -0,0 +1,14 @@
{
"plugins": [
"transform-object-rest-spread"
],
"presets": [
["env", {
"targets": {
"node": "8"
}
}]
],
"sourceMaps": "inline",
"retainLines": true
}
+3 -3
View File
@@ -13,6 +13,7 @@
"Item": true,
"Container": true,
"equal": true,
"expectAsync": true,
"notEqual": true,
"it_only_db": true,
"it_exclude_dbs": true,
@@ -20,12 +21,11 @@
"describe_only": true,
"on_db": true,
"defaultConfiguration": true,
"expectSuccess": true,
"range": true,
"expectError": true,
"jequal": true,
"create": true,
"arrayContains": true
"arrayContains": true,
"expectAsync": true
},
"rules": {
"no-console": [0],
+139 -58
View File
@@ -1,6 +1,6 @@
"use strict";
'use strict';
const Config = require("../src/Config");
const Config = require('../lib/Config');
const loginWithWrongCredentialsShouldFail = function(username, password) {
return new Promise((resolve, reject) => {
@@ -22,7 +22,12 @@ const isAccountLockoutError = function(username, password, duration, waitTime) {
Parse.User.logIn(username, password)
.then(() => reject('login should have failed'))
.catch(err => {
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
if (
err.message ===
'Your account is locked due to multiple failed login attempts. Please try again after ' +
duration +
' minute(s)'
) {
resolve();
} else {
reject(err);
@@ -32,8 +37,7 @@ const isAccountLockoutError = function(username, password, duration, waitTime) {
});
};
describe("Account Lockout Policy: ", () => {
describe('Account Lockout Policy: ', () => {
it('account should not be locked even after failed login attempts if account lockout policy is not set', done => {
reconfigureServer({
appName: 'unlimited',
@@ -46,17 +50,28 @@ describe("Account Lockout Policy: ", () => {
return user.signUp(null);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
return loginWithWrongCredentialsShouldFail(
'username1',
'incorrect password 1'
);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
return loginWithWrongCredentialsShouldFail(
'username1',
'incorrect password 2'
);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
return loginWithWrongCredentialsShouldFail(
'username1',
'incorrect password 3'
);
})
.then(() => done())
.catch(err => {
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
fail(
'allow unlimited failed login attempts failed: ' + JSON.stringify(err)
);
done();
});
});
@@ -66,9 +81,9 @@ describe("Account Lockout Policy: ", () => {
appName: 'duration',
accountLockout: {
duration: 'invalid value',
threshold: 5
threshold: 5,
},
publicServerURL: "https://my.public.server.com/1"
publicServerURL: 'https://my.public.server.com/1',
})
.then(() => {
Config.get('test');
@@ -76,10 +91,17 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
if (
err &&
err ===
'Account lockout duration should be greater than 0 and less than 100000'
) {
done();
} else {
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
fail(
'set duration to an invalid number test failed: ' +
JSON.stringify(err)
);
done();
}
});
@@ -90,9 +112,9 @@ describe("Account Lockout Policy: ", () => {
appName: 'threshold',
accountLockout: {
duration: 5,
threshold: 'invalid number'
threshold: 'invalid number',
},
publicServerURL: "https://my.public.server.com/1"
publicServerURL: 'https://my.public.server.com/1',
})
.then(() => {
Config.get('test');
@@ -100,10 +122,17 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
if (
err &&
err ===
'Account lockout threshold should be an integer greater than 0 and less than 1000'
) {
done();
} else {
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
fail(
'set threshold to an invalid number test failed: ' +
JSON.stringify(err)
);
done();
}
});
@@ -114,9 +143,9 @@ describe("Account Lockout Policy: ", () => {
appName: 'threshold',
accountLockout: {
duration: 5,
threshold: 0
threshold: 0,
},
publicServerURL: "https://my.public.server.com/1"
publicServerURL: 'https://my.public.server.com/1',
})
.then(() => {
Config.get('test');
@@ -124,10 +153,16 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
if (
err &&
err ===
'Account lockout threshold should be an integer greater than 0 and less than 1000'
) {
done();
} else {
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
fail(
'threshold value < 1 is invalid test failed: ' + JSON.stringify(err)
);
done();
}
});
@@ -138,9 +173,9 @@ describe("Account Lockout Policy: ", () => {
appName: 'threshold',
accountLockout: {
duration: 5,
threshold: 1000
threshold: 1000,
},
publicServerURL: "https://my.public.server.com/1"
publicServerURL: 'https://my.public.server.com/1',
})
.then(() => {
Config.get('test');
@@ -148,10 +183,17 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
if (
err &&
err ===
'Account lockout threshold should be an integer greater than 0 and less than 1000'
) {
done();
} else {
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
fail(
'threshold value > 999 is invalid test failed: ' +
JSON.stringify(err)
);
done();
}
});
@@ -162,9 +204,9 @@ describe("Account Lockout Policy: ", () => {
appName: 'duration',
accountLockout: {
duration: 0,
threshold: 5
threshold: 5,
},
publicServerURL: "https://my.public.server.com/1"
publicServerURL: 'https://my.public.server.com/1',
})
.then(() => {
Config.get('test');
@@ -172,10 +214,16 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
if (
err &&
err ===
'Account lockout duration should be greater than 0 and less than 100000'
) {
done();
} else {
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
fail(
'duration value < 1 is invalid test failed: ' + JSON.stringify(err)
);
done();
}
});
@@ -186,9 +234,9 @@ describe("Account Lockout Policy: ", () => {
appName: 'duration',
accountLockout: {
duration: 100000,
threshold: 5
threshold: 5,
},
publicServerURL: "https://my.public.server.com/1"
publicServerURL: 'https://my.public.server.com/1',
})
.then(() => {
Config.get('test');
@@ -196,10 +244,17 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
if (
err &&
err ===
'Account lockout duration should be greater than 0 and less than 100000'
) {
done();
} else {
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
fail(
'duration value > 99999 is invalid test failed: ' +
JSON.stringify(err)
);
done();
}
});
@@ -210,21 +265,27 @@ describe("Account Lockout Policy: ", () => {
appName: 'lockout threshold',
accountLockout: {
duration: 1,
threshold: 2
threshold: 2,
},
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
const user = new Parse.User();
user.setUsername("username2");
user.setPassword("failedLoginAttemptsThreshold");
user.setUsername('username2');
user.setPassword('failedLoginAttemptsThreshold');
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
return loginWithWrongCredentialsShouldFail(
'username2',
'wrong password'
);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
return loginWithWrongCredentialsShouldFail(
'username2',
'wrong password'
);
})
.then(() => {
return isAccountLockoutError('username2', 'wrong password', 1, 1);
@@ -233,7 +294,10 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
fail(
'lock account after failed login attempts test failed: ' +
JSON.stringify(err)
);
done();
});
});
@@ -243,34 +307,43 @@ describe("Account Lockout Policy: ", () => {
appName: 'lockout threshold',
accountLockout: {
duration: 0.05, // 0.05*60 = 3 secs
threshold: 2
threshold: 2,
},
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
const user = new Parse.User();
user.setUsername("username3");
user.setPassword("failedLoginAttemptsThreshold");
user.setUsername('username3');
user.setPassword('failedLoginAttemptsThreshold');
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
return loginWithWrongCredentialsShouldFail(
'username3',
'wrong password'
);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
return loginWithWrongCredentialsShouldFail(
'username3',
'wrong password'
);
})
.then(() => {
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
})
.then(() => {
// account should still be locked even after 2 seconds.
// account should still be locked even after 2 seconds.
return isAccountLockoutError('username3', 'wrong password', 0.05, 2000);
})
.then(() => {
done();
})
.catch(err => {
fail('account should be locked for duration mins test failed: ' + JSON.stringify(err));
fail(
'account should be locked for duration mins test failed: ' +
JSON.stringify(err)
);
done();
});
});
@@ -280,24 +353,30 @@ describe("Account Lockout Policy: ", () => {
appName: 'lockout threshold',
accountLockout: {
duration: 0.05, // 0.05*60 = 3 secs
threshold: 2
threshold: 2,
},
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
const user = new Parse.User();
user.setUsername("username4");
user.setPassword("correct password");
user.setUsername('username4');
user.setPassword('correct password');
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
return loginWithWrongCredentialsShouldFail(
'username4',
'wrong password'
);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
return loginWithWrongCredentialsShouldFail(
'username4',
'wrong password'
);
})
.then(() => {
// allow locked user to login after 3 seconds with a valid userid and password
// allow locked user to login after 3 seconds with a valid userid and password
return new Promise((resolve, reject) => {
setTimeout(() => {
Parse.User.logIn('username4', 'correct password')
@@ -310,9 +389,11 @@ describe("Account Lockout Policy: ", () => {
done();
})
.catch(err => {
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
fail(
'allow login for locked account after accountPolicy.duration minutes test failed: ' +
JSON.stringify(err)
);
done();
});
});
})
});
+24 -23
View File
@@ -1,28 +1,29 @@
const AdaptableController = require("../src/Controllers/AdaptableController").AdaptableController;
const FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
const FilesController = require("../src/Controllers/FilesController").FilesController;
const AdaptableController = require('../lib/Controllers/AdaptableController')
.AdaptableController;
const FilesAdapter = require('../lib/Adapters/Files/FilesAdapter').default;
const FilesController = require('../lib/Controllers/FilesController')
.FilesController;
const MockController = function(options) {
AdaptableController.call(this, options);
}
};
MockController.prototype = Object.create(AdaptableController.prototype);
MockController.prototype.constructor = AdaptableController;
describe("AdaptableController", ()=>{
it("should use the provided adapter", (done) => {
describe('AdaptableController', () => {
it('should use the provided adapter', done => {
const adapter = new FilesAdapter();
const controller = new FilesController(adapter);
expect(controller.adapter).toBe(adapter);
// make sure _adapter is private
expect(controller._adapter).toBe(undefined);
// Override _adapter is not doing anything
controller._adapter = "Hello";
controller._adapter = 'Hello';
expect(controller.adapter).toBe(adapter);
done();
});
it("should throw when creating a new mock controller", (done) => {
it('should throw when creating a new mock controller', done => {
const adapter = new FilesAdapter();
expect(() => {
new MockController(adapter);
@@ -30,7 +31,7 @@ describe("AdaptableController", ()=>{
done();
});
it("should fail setting the wrong adapter to the controller", (done) => {
it('should fail setting the wrong adapter to the controller', done => {
function WrongAdapter() {}
const adapter = new FilesAdapter();
const controller = new FilesController(adapter);
@@ -41,7 +42,7 @@ describe("AdaptableController", ()=>{
done();
});
it("should fail to instantiate a controller with wrong adapter", (done) => {
it('should fail to instantiate a controller with wrong adapter', done => {
function WrongAdapter() {}
const adapter = new WrongAdapter();
expect(() => {
@@ -50,32 +51,32 @@ describe("AdaptableController", ()=>{
done();
});
it("should fail to instantiate a controller without an adapter", (done) => {
it('should fail to instantiate a controller without an adapter', done => {
expect(() => {
new FilesController();
}).toThrow();
done();
});
it("should accept an object adapter", (done) => {
it('should accept an object adapter', done => {
const adapter = {
createFile: function() { },
deleteFile: function() { },
getFileData: function() { },
getFileLocation: function() { },
}
createFile: function() {},
deleteFile: function() {},
getFileData: function() {},
getFileLocation: function() {},
};
expect(() => {
new FilesController(adapter);
}).not.toThrow();
done();
});
it("should accept an object adapter", (done) => {
it('should accept an prototype based object adapter', done => {
function AGoodAdapter() {}
AGoodAdapter.prototype.createFile = function() { };
AGoodAdapter.prototype.deleteFile = function() { };
AGoodAdapter.prototype.getFileData = function() { };
AGoodAdapter.prototype.getFileLocation = function() { };
AGoodAdapter.prototype.createFile = function() {};
AGoodAdapter.prototype.deleteFile = function() {};
AGoodAdapter.prototype.getFileData = function() {};
AGoodAdapter.prototype.getFileLocation = function() {};
const adapter = new AGoodAdapter();
expect(() => {
+46 -46
View File
@@ -1,41 +1,41 @@
const loadAdapter = require('../lib/Adapters/AdapterLoader').loadAdapter;
const FilesAdapter = require('@parse/fs-files-adapter').default;
const S3Adapter = require('@parse/s3-files-adapter').default;
const ParsePushAdapter = require('@parse/push-adapter').default;
const Config = require('../lib/Config');
const loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
const FilesAdapter = require("@parse/fs-files-adapter").default;
const S3Adapter = require("@parse/s3-files-adapter").default;
const ParsePushAdapter = require("@parse/push-adapter").default;
const Config = require('../src/Config');
describe("AdapterLoader", ()=>{
it("should instantiate an adapter from string in object", (done) => {
const adapterPath = require('path').resolve("./spec/MockAdapter");
describe('AdapterLoader', () => {
it('should instantiate an adapter from string in object', done => {
const adapterPath = require('path').resolve('./spec/MockAdapter');
const adapter = loadAdapter({
adapter: adapterPath,
options: {
key: "value",
foo: "bar"
}
key: 'value',
foo: 'bar',
},
});
expect(adapter instanceof Object).toBe(true);
expect(adapter.options.key).toBe("value");
expect(adapter.options.foo).toBe("bar");
expect(adapter.options.key).toBe('value');
expect(adapter.options.foo).toBe('bar');
done();
});
it("should instantiate an adapter from string", (done) => {
const adapterPath = require('path').resolve("./spec/MockAdapter");
it('should instantiate an adapter from string', done => {
const adapterPath = require('path').resolve('./spec/MockAdapter');
const adapter = loadAdapter(adapterPath);
expect(adapter instanceof Object).toBe(true);
done();
});
it("should instantiate an adapter from string that is module", (done) => {
const adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
it('should instantiate an adapter from string that is module', done => {
const adapterPath = require('path').resolve(
'./lib/Adapters/Files/FilesAdapter'
);
const adapter = loadAdapter({
adapter: adapterPath
adapter: adapterPath,
});
expect(typeof adapter).toBe('object');
@@ -46,9 +46,9 @@ describe("AdapterLoader", ()=>{
done();
});
it("should instantiate an adapter from npm module", (done) => {
it('should instantiate an adapter from npm module', done => {
const adapter = loadAdapter({
module: '@parse/fs-files-adapter'
module: '@parse/fs-files-adapter',
});
expect(typeof adapter).toBe('object');
@@ -59,59 +59,59 @@ describe("AdapterLoader", ()=>{
done();
});
it("should instantiate an adapter from function/Class", (done) => {
it('should instantiate an adapter from function/Class', done => {
const adapter = loadAdapter({
adapter: FilesAdapter
adapter: FilesAdapter,
});
expect(adapter instanceof FilesAdapter).toBe(true);
done();
});
it("should instantiate the default adapter from Class", (done) => {
it('should instantiate the default adapter from Class', done => {
const adapter = loadAdapter(null, FilesAdapter);
expect(adapter instanceof FilesAdapter).toBe(true);
done();
});
it("should use the default adapter", (done) => {
it('should use the default adapter', done => {
const defaultAdapter = new FilesAdapter();
const adapter = loadAdapter(null, defaultAdapter);
expect(adapter instanceof FilesAdapter).toBe(true);
done();
});
it("should use the provided adapter", (done) => {
it('should use the provided adapter', done => {
const originalAdapter = new FilesAdapter();
const adapter = loadAdapter(originalAdapter);
expect(adapter).toBe(originalAdapter);
done();
});
it("should fail loading an improperly configured adapter", (done) => {
it('should fail loading an improperly configured adapter', done => {
const Adapter = function(options) {
if (!options.foo) {
throw "foo is required for that adapter";
throw 'foo is required for that adapter';
}
}
};
const adapterOptions = {
param: "key",
doSomething: function() {}
param: 'key',
doSomething: function() {},
};
expect(() => {
const adapter = loadAdapter(adapterOptions, Adapter);
expect(adapter).toEqual(adapterOptions);
}).not.toThrow("foo is required for that adapter");
}).not.toThrow('foo is required for that adapter');
done();
});
it("should load push adapter from options", (done) => {
it('should load push adapter from options', done => {
const options = {
android: {
senderId: 'yolo',
apiKey: 'yolo'
}
}
apiKey: 'yolo',
},
};
expect(() => {
const adapter = loadAdapter(undefined, ParsePushAdapter, options);
expect(adapter.constructor).toBe(ParsePushAdapter);
@@ -120,16 +120,16 @@ describe("AdapterLoader", ()=>{
done();
});
it("should load custom push adapter from string (#3544)", (done) => {
const adapterPath = require('path').resolve("./spec/MockPushAdapter");
it('should load custom push adapter from string (#3544)', done => {
const adapterPath = require('path').resolve('./spec/MockPushAdapter');
const options = {
ios: {
bundleId: 'bundle.id'
}
}
bundleId: 'bundle.id',
},
};
const pushAdapterOptions = {
adapter: adapterPath,
options
options,
};
expect(() => {
reconfigureServer({
@@ -144,12 +144,12 @@ describe("AdapterLoader", ()=>{
}).not.toThrow();
});
it("should load S3Adapter from direct passing", (done) => {
const s3Adapter = new S3Adapter("key", "secret", "bucket")
it('should load S3Adapter from direct passing', done => {
const s3Adapter = new S3Adapter('key', 'secret', 'bucket');
expect(() => {
const adapter = loadAdapter(s3Adapter, FilesAdapter);
expect(adapter).toBe(s3Adapter);
}).not.toThrow();
done();
})
});
});
+82
View File
@@ -0,0 +1,82 @@
const AggregateRouter = require('../lib/Routers/AggregateRouter')
.AggregateRouter;
describe('AggregateRouter', () => {
it('get pipeline from Array', () => {
const body = [
{
group: { objectId: {} },
},
];
const expected = [{ $group: { _id: {} } }];
const result = AggregateRouter.getPipeline(body);
expect(result).toEqual(expected);
});
it('get pipeline from Object', () => {
const body = {
group: { objectId: {} },
};
const expected = [{ $group: { _id: {} } }];
const result = AggregateRouter.getPipeline(body);
expect(result).toEqual(expected);
});
it('get pipeline from Pipeline Operator (Array)', () => {
const body = {
pipeline: [
{
group: { objectId: {} },
},
],
};
const expected = [{ $group: { _id: {} } }];
const result = AggregateRouter.getPipeline(body);
expect(result).toEqual(expected);
});
it('get pipeline from Pipeline Operator (Object)', () => {
const body = {
pipeline: {
group: { objectId: {} },
},
};
const expected = [{ $group: { _id: {} } }];
const result = AggregateRouter.getPipeline(body);
expect(result).toEqual(expected);
});
it('get pipeline fails multiple keys in Array stage ', () => {
const body = [
{
group: { objectId: {} },
match: { name: 'Test' },
},
];
try {
AggregateRouter.getPipeline(body);
} catch (e) {
expect(e.message).toBe(
'Pipeline stages should only have one key found group, match'
);
}
});
it('get pipeline fails multiple keys in Pipeline Operator Array stage ', () => {
const body = {
pipeline: [
{
group: { objectId: {} },
match: { name: 'Test' },
},
],
};
try {
AggregateRouter.getPipeline(body);
} catch (e) {
expect(e.message).toBe(
'Pipeline stages should only have one key found group, match'
);
}
});
});
+57 -49
View File
@@ -1,61 +1,69 @@
const analyticsAdapter = {
appOpened: function() {},
trackEvent: function() {}
}
trackEvent: function() {},
};
describe('AnalyticsController', () => {
it('should track a simple event', (done) => {
it('should track a simple event', done => {
spyOn(analyticsAdapter, 'trackEvent').and.callThrough();
reconfigureServer({
analyticsAdapter
}).then(() => {
return Parse.Analytics.track('MyEvent', {
key: 'value',
count: '0'
})
}).then(() => {
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
const lastCall = analyticsAdapter.trackEvent.calls.first();
const args = lastCall.args;
expect(args[0]).toEqual('MyEvent');
expect(args[1]).toEqual({
dimensions: {
key: 'value',
count: '0'
}
});
done();
}, (err) => {
fail(JSON.stringify(err));
done();
analyticsAdapter,
})
.then(() => {
return Parse.Analytics.track('MyEvent', {
key: 'value',
count: '0',
});
})
.then(
() => {
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
const lastCall = analyticsAdapter.trackEvent.calls.first();
const args = lastCall.args;
expect(args[0]).toEqual('MyEvent');
expect(args[1]).toEqual({
dimensions: {
key: 'value',
count: '0',
},
});
done();
},
err => {
fail(JSON.stringify(err));
done();
}
);
});
it('should track a app opened event', (done) => {
it('should track a app opened event', done => {
spyOn(analyticsAdapter, 'appOpened').and.callThrough();
reconfigureServer({
analyticsAdapter
}).then(() => {
return Parse.Analytics.track('AppOpened', {
key: 'value',
count: '0'
})
}).then(() => {
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
const lastCall = analyticsAdapter.appOpened.calls.first();
const args = lastCall.args;
expect(args[0]).toEqual({
dimensions: {
key: 'value',
count: '0'
}
});
done();
}, (err) => {
fail(JSON.stringify(err));
done();
analyticsAdapter,
})
})
})
.then(() => {
return Parse.Analytics.track('AppOpened', {
key: 'value',
count: '0',
});
})
.then(
() => {
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
const lastCall = analyticsAdapter.appOpened.calls.first();
const args = lastCall.args;
expect(args[0]).toEqual({
dimensions: {
key: 'value',
count: '0',
},
});
done();
},
err => {
fail(JSON.stringify(err));
done();
}
);
});
});
+304 -175
View File
@@ -1,59 +1,67 @@
const auth = require('../src/Auth');
const Config = require('../src/Config');
const rest = require('../src/rest');
const AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
const auth = require('../lib/Auth');
const Config = require('../lib/Config');
const rest = require('../lib/rest');
const request = require('../lib/request');
const AudiencesRouter = require('../lib/Routers/AudiencesRouter')
.AudiencesRouter;
describe('AudiencesRouter', () => {
it('uses find condition from request.body', (done) => {
it('uses find condition from request.body', done => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
name: 'Android Users',
query: '{ "test": "android" }',
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
name: 'Iphone Users',
query: '{ "test": "ios" }',
};
const request = {
config: config,
auth: auth.master(config),
body: {
where: {
query: '{ "test": "android" }'
}
query: '{ "test": "android" }',
},
},
query: {},
info: {}
info: {},
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
rest
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
return rest.create(
config,
auth.nobody(config),
'_Audience',
iosAudienceRequest
);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
.then(res => {
const results = res.response.results;
expect(results.length).toEqual(1);
done();
})
.catch((err) => {
.catch(err => {
fail(JSON.stringify(err));
done();
});
});
it('uses find condition from request.query', (done) => {
it('uses find condition from request.query', done => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
name: 'Android Users',
query: '{ "test": "android" }',
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
name: 'Iphone Users',
query: '{ "test": "ios" }',
};
const request = {
config: config,
@@ -61,96 +69,116 @@ describe('AudiencesRouter', () => {
body: {},
query: {
where: {
'query': '{ "test": "android" }'
}
query: '{ "test": "android" }',
},
},
info: {}
info: {},
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
rest
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
return rest.create(
config,
auth.nobody(config),
'_Audience',
iosAudienceRequest
);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
.then(res => {
const results = res.response.results;
expect(results.length).toEqual(1);
done();
})
.catch((err) => {
.catch(err => {
fail(err);
done();
});
});
it('query installations with limit = 0', (done) => {
it('query installations with limit = 0', done => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
name: 'Android Users',
query: '{ "test": "android" }',
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
name: 'Iphone Users',
query: '{ "test": "ios" }',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0
limit: 0,
},
info: {}
info: {},
};
Config.get('test');
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
rest
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
return rest.create(
config,
auth.nobody(config),
'_Audience',
iosAudienceRequest
);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
.then(res => {
const response = res.response;
expect(response.results.length).toEqual(0);
done();
})
.catch((err) => {
.catch(err => {
fail(JSON.stringify(err));
done();
});
});
it('query installations with count = 1', done => {
it_exclude_dbs(['postgres'])('query installations with count = 1', done => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
name: 'Android Users',
query: '{ "test": "android" }',
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
name: 'Iphone Users',
query: '{ "test": "ios" }',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
count: 1
count: 1,
},
info: {}
info: {},
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
rest
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() =>
rest.create(
config,
auth.nobody(config),
'_Audience',
iosAudienceRequest
)
)
.then(() => router.handleFind(request))
.then((res) => {
.then(res => {
const response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(2);
@@ -159,66 +187,110 @@ describe('AudiencesRouter', () => {
.catch(error => {
fail(JSON.stringify(error));
done();
})
});
it('query installations with limit = 0 and count = 1', (done) => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0,
count: 1
},
info: {}
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
const response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
})
.catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('should create, read, update and delete audiences throw api', (done) => {
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
.then(() => {
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
it_exclude_dbs(['postgres'])(
'query installations with limit = 0 and count = 1',
done => {
const config = Config.get('test');
const androidAudienceRequest = {
name: 'Android Users',
query: '{ "test": "android" }',
};
const iosAudienceRequest = {
name: 'Iphone Users',
query: '{ "test": "ios" }',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0,
count: 1,
},
info: {},
};
const router = new AudiencesRouter();
rest
.create(
config,
auth.nobody(config),
'_Audience',
androidAudienceRequest
)
.then(() => {
return rest.create(
config,
auth.nobody(config),
'_Audience',
iosAudienceRequest
);
})
.then(() => {
return router.handleFind(request);
})
.then(res => {
const response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
})
.catch(err => {
fail(JSON.stringify(err));
done();
});
}
);
it('should create, read, update and delete audiences throw api', done => {
Parse._request(
'POST',
'push_audiences',
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
{ useMasterKey: true }
).then(() => {
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then(
results => {
expect(results.results.length).toEqual(1);
expect(results.results[0].name).toEqual('My Audience');
expect(results.results[0].query.deviceType).toEqual('ios');
Parse._request('GET', `push_audiences/${results.results[0].objectId}`, {}, { useMasterKey: true }).then((results) => {
Parse._request(
'GET',
`push_audiences/${results.results[0].objectId}`,
{},
{ useMasterKey: true }
).then(results => {
expect(results.name).toEqual('My Audience');
expect(results.query.deviceType).toEqual('ios');
Parse._request('PUT', `push_audiences/${results.objectId}`, { name: 'My Audience 2' }, { useMasterKey: true }).then(() => {
Parse._request('GET', `push_audiences/${results.objectId}`, {}, { useMasterKey: true }).then((results) => {
Parse._request(
'PUT',
`push_audiences/${results.objectId}`,
{ name: 'My Audience 2' },
{ useMasterKey: true }
).then(() => {
Parse._request(
'GET',
`push_audiences/${results.objectId}`,
{},
{ useMasterKey: true }
).then(results => {
expect(results.name).toEqual('My Audience 2');
expect(results.query.deviceType).toEqual('ios');
Parse._request('DELETE', `push_audiences/${results.objectId}`, {}, { useMasterKey: true }).then(() => {
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
Parse._request(
'DELETE',
`push_audiences/${results.objectId}`,
{},
{ useMasterKey: true }
).then(() => {
Parse._request(
'GET',
'push_audiences',
{},
{ useMasterKey: true }
).then(results => {
expect(results.results.length).toEqual(0);
done();
});
@@ -226,112 +298,169 @@ describe('AudiencesRouter', () => {
});
});
});
});
});
});
it('should only create with master key', (done) => {
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })})
.then(
() => {},
(error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
});
});
it('should only find with master key', (done) => {
Parse._request('GET', 'push_audiences', {})
.then(
() => {},
(error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
it('should only create with master key', done => {
Parse._request('POST', 'push_audiences', {
name: 'My Audience',
query: JSON.stringify({ deviceType: 'ios' }),
}).then(
() => {},
error => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
});
it('should only get with master key', (done) => {
Parse._request('GET', `push_audiences/someId`, {})
.then(
() => {},
(error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
it('should only find with master key', done => {
Parse._request('GET', 'push_audiences', {}).then(
() => {},
error => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
});
it('should only update with master key', (done) => {
Parse._request('PUT', `push_audiences/someId`, { name: 'My Audience 2' })
.then(
() => {},
(error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
it('should only get with master key', done => {
Parse._request('GET', `push_audiences/someId`, {}).then(
() => {},
error => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
});
it('should only delete with master key', (done) => {
Parse._request('DELETE', `push_audiences/someId`, {})
.then(
() => {},
(error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
it('should only update with master key', done => {
Parse._request('PUT', `push_audiences/someId`, {
name: 'My Audience 2',
}).then(
() => {},
error => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
});
it_exclude_dbs(['postgres'])('should support legacy parse.com audience fields', (done) => {
const database = (Config.get(Parse.applicationId)).database.adapter.database;
const now = new Date();
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
.then((audience) => {
it('should only delete with master key', done => {
Parse._request('DELETE', `push_audiences/someId`, {}).then(
() => {},
error => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
}
);
});
it_exclude_dbs(['postgres'])(
'should support legacy parse.com audience fields',
done => {
const database = Config.get(Parse.applicationId).database.adapter
.database;
const now = new Date();
Parse._request(
'POST',
'push_audiences',
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
{ useMasterKey: true }
).then(audience => {
database.collection('test__Audience').updateOne(
{ _id: audience.objectId },
{
$set: {
times_used: 1,
_last_used: now
}
_last_used: now,
},
},
{},
(error) => {
expect(error).toEqual(null)
database.collection('test__Audience').find({ _id: audience.objectId}).toArray(
(error, rows) => {
expect(error).toEqual(null)
error => {
expect(error).toEqual(null);
database
.collection('test__Audience')
.find({ _id: audience.objectId })
.toArray((error, rows) => {
expect(error).toEqual(null);
expect(rows[0]['times_used']).toEqual(1);
expect(rows[0]['_last_used']).toEqual(now);
Parse._request('GET', 'push_audiences/' + audience.objectId, {}, {useMasterKey: true})
.then((audience) => {
Parse._request(
'GET',
'push_audiences/' + audience.objectId,
{},
{ useMasterKey: true }
)
.then(audience => {
expect(audience.name).toEqual('My Audience');
expect(audience.query.deviceType).toEqual('ios');
expect(audience.timesUsed).toEqual(1);
expect(audience.lastUsed).toEqual(now.toISOString());
done();
})
.catch((error) => { done.fail(error); })
.catch(error => {
done.fail(error);
});
});
});
}
);
});
}
);
it('should be able to search on audiences', done => {
Parse._request(
'POST',
'push_audiences',
{ name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' }) },
{ useMasterKey: true }
).then(() => {
const query = {
timesUsed: { $exists: false },
lastUsed: { $exists: false },
};
Parse._request(
'GET',
'push_audiences?order=-createdAt&limit=1',
{ where: query },
{ useMasterKey: true }
)
.then(results => {
expect(results.results.length).toEqual(1);
const audience = results.results[0];
expect(audience.name).toEqual('neverUsed');
done();
})
.catch(error => {
done.fail(error);
});
});
});
it('should be able to search on audiences', (done) => {
Parse._request('POST', 'push_audiences', { name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
.then(() => {
const query = {"timesUsed": {"$exists": false}, "lastUsed": {"$exists": false}};
Parse._request('GET', 'push_audiences?order=-createdAt&limit=1', {where: query}, {useMasterKey: true})
.then((results) => {
expect(results.results.length).toEqual(1);
const audience = results.results[0];
expect(audience.name).toEqual("neverUsed");
done();
})
.catch((error) => { done.fail(error); })
})
it('should handle _Audience invalid fields via rest', async () => {
await reconfigureServer({
appId: 'test',
restAPIKey: 'test',
publicServerURL: 'http://localhost:8378/1',
});
try {
await request({
method: 'POST',
url: 'http://localhost:8378/1/classes/_Audience',
body: { lorem: 'ipsum', _method: 'POST' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test',
'Content-Type': 'application/json',
},
});
expect(true).toBeFalsy();
} catch (e) {
expect(e.data.code).toBe(107);
expect(e.data.error).toBe('Could not add field lorem');
}
});
});
+183 -33
View File
@@ -1,6 +1,6 @@
describe('Auth', () => {
const Auth = require('../src/Auth.js').Auth;
const { Auth, getAuthForSessionToken } = require('../lib/Auth.js');
const Config = require('../lib/Config');
describe('getUserRoles', () => {
let auth;
let config;
@@ -14,45 +14,45 @@ describe('Auth', () => {
cacheController: {
role: {
get: () => Promise.resolve(currentRoles),
set: jasmine.createSpy('set')
}
}
}
set: jasmine.createSpy('set'),
},
},
};
spyOn(config.cacheController.role, 'get').and.callThrough();
auth = new Auth({
config: config,
isMaster: false,
user: {
id: currentUserId
id: currentUserId,
},
installationId: 'installationId'
installationId: 'installationId',
});
});
it('should get user roles from the cache', (done) => {
auth.getUserRoles()
.then((roles) => {
const firstSet = config.cacheController.role.set.calls.first();
expect(firstSet).toEqual(undefined);
it('should get user roles from the cache', done => {
auth.getUserRoles().then(roles => {
const firstSet = config.cacheController.role.set.calls.first();
expect(firstSet).toEqual(undefined);
const firstGet = config.cacheController.role.get.calls.first();
expect(firstGet.args[0]).toEqual(currentUserId);
expect(roles).toEqual(currentRoles);
done();
});
const firstGet = config.cacheController.role.get.calls.first();
expect(firstGet.args[0]).toEqual(currentUserId);
expect(roles).toEqual(currentRoles);
done();
});
});
it('should only query the roles once', (done) => {
it('should only query the roles once', done => {
const loadRolesSpy = spyOn(auth, '_loadRoles').and.callThrough();
auth.getUserRoles()
.then((roles) => {
auth
.getUserRoles()
.then(roles => {
expect(roles).toEqual(currentRoles);
return auth.getUserRoles()
return auth.getUserRoles();
})
.then(() => auth.getUserRoles())
.then(() => auth.getUserRoles())
.then((roles) => {
.then(roles => {
// Should only call the cache adapter once.
expect(config.cacheController.role.get.calls.count()).toEqual(1);
expect(loadRolesSpy.calls.count()).toEqual(1);
@@ -64,30 +64,180 @@ describe('Auth', () => {
});
});
it('should not have any roles with no user', (done) => {
auth.user = null
auth.getUserRoles()
.then((roles) => expect(roles).toEqual([]))
it('should not have any roles with no user', done => {
auth.user = null;
auth
.getUserRoles()
.then(roles => expect(roles).toEqual([]))
.then(() => done());
});
it('should not have any user roles with master', (done) => {
auth.isMaster = true
auth.getUserRoles()
.then((roles) => expect(roles).toEqual([]))
it('should not have any user roles with master', done => {
auth.isMaster = true;
auth
.getUserRoles()
.then(roles => expect(roles).toEqual([]))
.then(() => done());
});
it('should properly handle bcrypt upgrade', (done) => {
it('should properly handle bcrypt upgrade', done => {
const bcryptOriginal = require('bcrypt-nodejs');
const bcryptNew = require('bcryptjs');
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
bcryptNew.compare('my1Long:password', res, function(err, res) {
expect(res).toBeTruthy();
done();
})
});
});
});
});
it('should load auth without a config', async () => {
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
});
expect(userAuth.user instanceof Parse.User).toBe(true);
expect(userAuth.user.id).toBe(user.id);
});
it('should load auth with a config', async () => {
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
config: Config.get('test'),
});
expect(userAuth.user instanceof Parse.User).toBe(true);
expect(userAuth.user.id).toBe(user.id);
});
it('should load auth without a config', async () => {
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
});
expect(userAuth.user instanceof Parse.User).toBe(true);
expect(userAuth.user.id).toBe(user.id);
});
it('should load auth with a config', async () => {
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
config: Config.get('test'),
});
expect(userAuth.user instanceof Parse.User).toBe(true);
expect(userAuth.user.id).toBe(user.id);
});
describe('getRolesForUser', () => {
const rolesNumber = 300;
it('should load all roles without config', async () => {
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
});
const roles = [];
for (let i = 0; i < rolesNumber; i++) {
const acl = new Parse.ACL();
const role = new Parse.Role('roleloadtest' + i, acl);
role.getUsers().add([user]);
roles.push(role.save());
}
const savedRoles = await Promise.all(roles);
expect(savedRoles.length).toBe(rolesNumber);
const cloudRoles = await userAuth.getRolesForUser();
expect(cloudRoles.length).toBe(rolesNumber);
});
it('should load all roles with config', async () => {
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
config: Config.get('test'),
});
const roles = [];
for (let i = 0; i < rolesNumber; i++) {
const acl = new Parse.ACL();
const role = new Parse.Role('roleloadtest' + i, acl);
role.getUsers().add([user]);
roles.push(role.save());
}
const savedRoles = await Promise.all(roles);
expect(savedRoles.length).toBe(rolesNumber);
const cloudRoles = await userAuth.getRolesForUser();
expect(cloudRoles.length).toBe(rolesNumber);
});
it('should load all roles for different users with config', async () => {
const rolesNumber = 100;
const user = new Parse.User();
await user.signUp({
username: 'hello',
password: 'password',
});
const user2 = new Parse.User();
await user2.signUp({
username: 'world',
password: '1234',
});
expect(user.getSessionToken()).not.toBeUndefined();
const userAuth = await getAuthForSessionToken({
sessionToken: user.getSessionToken(),
config: Config.get('test'),
});
const user2Auth = await getAuthForSessionToken({
sessionToken: user2.getSessionToken(),
config: Config.get('test'),
});
const roles = [];
for (let i = 0; i < rolesNumber; i += 1) {
const acl = new Parse.ACL();
const acl2 = new Parse.ACL();
const role = new Parse.Role('roleloadtest' + i, acl);
const role2 = new Parse.Role('role2loadtest' + i, acl2);
role.getUsers().add([user]);
role2.getUsers().add([user2]);
roles.push(role.save());
roles.push(role2.save());
}
const savedRoles = await Promise.all(roles);
expect(savedRoles.length).toBe(rolesNumber * 2);
const cloudRoles = await userAuth.getRolesForUser();
const cloudRoles2 = await user2Auth.getRolesForUser();
expect(cloudRoles.length).toBe(rolesNumber);
expect(cloudRoles2.length).toBe(rolesNumber);
});
});
});
File diff suppressed because it is too large Load Diff
+181 -50
View File
@@ -1,15 +1,18 @@
'use strict';
import commander from '../src/cli/utils/commander';
import definitions from '../src/cli/definitions/parse-server';
import liveQueryDefinitions from '../src/cli/definitions/parse-live-query-server';
const commander = require('../lib/cli/utils/commander').default;
const definitions = require('../lib/cli/definitions/parse-server').default;
const liveQueryDefinitions = require('../lib/cli/definitions/parse-live-query-server')
.default;
const path = require('path');
const { spawn } = require('child_process');
const testDefinitions = {
'arg0': 'PROGRAM_ARG_0',
'arg1': {
arg0: 'PROGRAM_ARG_0',
arg1: {
env: 'PROGRAM_ARG_1',
required: true
required: true,
},
'arg2': {
arg2: {
env: 'PROGRAM_ARG_2',
action: function(value) {
const intValue = parseInt(value);
@@ -17,16 +20,16 @@ const testDefinitions = {
throw 'arg2 is invalid';
}
return intValue;
}
},
},
arg3: {},
arg4: {
default: 'arg4Value',
},
'arg3': {},
'arg4': {
default: 'arg4Value'
}
};
describe('commander additions', () => {
afterEach((done) => {
afterEach(done => {
commander.options = [];
delete commander.arg0;
delete commander.arg1;
@@ -36,9 +39,20 @@ describe('commander additions', () => {
done();
});
it('should load properly definitions from args', (done) => {
it('should load properly definitions from args', done => {
commander.loadDefinitions(testDefinitions);
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg1', 'arg1Value', '--arg2', '2', '--arg3', 'some']);
commander.parse([
'node',
'./CLI.spec.js',
'--arg0',
'arg0Value',
'--arg1',
'arg1Value',
'--arg2',
'2',
'--arg3',
'some',
]);
expect(commander.arg0).toEqual('arg0Value');
expect(commander.arg1).toEqual('arg1Value');
expect(commander.arg2).toEqual(2);
@@ -47,12 +61,12 @@ describe('commander additions', () => {
done();
});
it('should load properly definitions from env', (done) => {
it('should load properly definitions from env', done => {
commander.loadDefinitions(testDefinitions);
commander.parse([], {
'PROGRAM_ARG_0': 'arg0ENVValue',
'PROGRAM_ARG_1': 'arg1ENVValue',
'PROGRAM_ARG_2': '3',
PROGRAM_ARG_0: 'arg0ENVValue',
PROGRAM_ARG_1: 'arg1ENVValue',
PROGRAM_ARG_2: '3',
});
expect(commander.arg0).toEqual('arg0ENVValue');
expect(commander.arg1).toEqual('arg1ENVValue');
@@ -61,14 +75,17 @@ describe('commander additions', () => {
done();
});
it('should load properly use args over env', (done) => {
it('should load properly use args over env', done => {
commander.loadDefinitions(testDefinitions);
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg4', ''], {
'PROGRAM_ARG_0': 'arg0ENVValue',
'PROGRAM_ARG_1': 'arg1ENVValue',
'PROGRAM_ARG_2': '4',
'PROGRAM_ARG_4': 'arg4ENVValue'
});
commander.parse(
['node', './CLI.spec.js', '--arg0', 'arg0Value', '--arg4', ''],
{
PROGRAM_ARG_0: 'arg0ENVValue',
PROGRAM_ARG_1: 'arg1ENVValue',
PROGRAM_ARG_2: '4',
PROGRAM_ARG_4: 'arg4ENVValue',
}
);
expect(commander.arg0).toEqual('arg0Value');
expect(commander.arg1).toEqual('arg1ENVValue');
expect(commander.arg2).toEqual(4);
@@ -76,24 +93,33 @@ describe('commander additions', () => {
done();
});
it('should fail in action as port is invalid', (done) => {
it('should fail in action as port is invalid', done => {
commander.loadDefinitions(testDefinitions);
expect(()=> {
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value'], {
'PROGRAM_ARG_0': 'arg0ENVValue',
'PROGRAM_ARG_1': 'arg1ENVValue',
'PROGRAM_ARG_2': 'hello',
expect(() => {
commander.parse(['node', './CLI.spec.js', '--arg0', 'arg0Value'], {
PROGRAM_ARG_0: 'arg0ENVValue',
PROGRAM_ARG_1: 'arg1ENVValue',
PROGRAM_ARG_2: 'hello',
});
}).toThrow('arg2 is invalid');
done();
});
it('should not override config.json', (done) => {
it('should not override config.json', done => {
commander.loadDefinitions(testDefinitions);
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', './spec/configs/CLIConfig.json'], {
'PROGRAM_ARG_0': 'arg0ENVValue',
'PROGRAM_ARG_1': 'arg1ENVValue',
});
commander.parse(
[
'node',
'./CLI.spec.js',
'--arg0',
'arg0Value',
'./spec/configs/CLIConfig.json',
],
{
PROGRAM_ARG_0: 'arg0ENVValue',
PROGRAM_ARG_1: 'arg1ENVValue',
}
);
const options = commander.getOptions();
expect(options.arg2).toBe(8888);
expect(options.arg3).toBe('hello'); //config value
@@ -101,28 +127,45 @@ describe('commander additions', () => {
done();
});
it('should fail with invalid values in JSON', (done) => {
it('should fail with invalid values in JSON', done => {
commander.loadDefinitions(testDefinitions);
expect(() => {
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', './spec/configs/CLIConfigFail.json'], {
'PROGRAM_ARG_0': 'arg0ENVValue',
'PROGRAM_ARG_1': 'arg1ENVValue',
});
commander.parse(
[
'node',
'./CLI.spec.js',
'--arg0',
'arg0Value',
'./spec/configs/CLIConfigFail.json',
],
{
PROGRAM_ARG_0: 'arg0ENVValue',
PROGRAM_ARG_1: 'arg1ENVValue',
}
);
}).toThrow('arg2 is invalid');
done();
});
it('should fail when too many apps are set', (done) => {
it('should fail when too many apps are set', done => {
commander.loadDefinitions(testDefinitions);
expect(() => {
commander.parse(['node','./CLI.spec.js','./spec/configs/CLIConfigFailTooManyApps.json']);
commander.parse([
'node',
'./CLI.spec.js',
'./spec/configs/CLIConfigFailTooManyApps.json',
]);
}).toThrow('Multiple apps are not supported');
done();
});
it('should load config from apps', (done) => {
it('should load config from apps', done => {
commander.loadDefinitions(testDefinitions);
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigApps.json']);
commander.parse([
'node',
'./CLI.spec.js',
'./spec/configs/CLIConfigApps.json',
]);
const options = commander.getOptions();
expect(options.arg1).toBe('my_app');
expect(options.arg2).toBe(8888);
@@ -131,10 +174,14 @@ describe('commander additions', () => {
done();
});
it('should fail when passing an invalid arguement', (done) => {
it('should fail when passing an invalid arguement', done => {
commander.loadDefinitions(testDefinitions);
expect(() => {
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigUnknownArg.json']);
commander.parse([
'node',
'./CLI.spec.js',
'./spec/configs/CLIConfigUnknownArg.json',
]);
}).toThrow('error: unknown option myArg');
done();
});
@@ -160,7 +207,7 @@ describe('definitions', () => {
it('should throw when using deprecated facebookAppIds', () => {
expect(() => {
definitions.facebookAppIds.action()
definitions.facebookAppIds.action();
}).toThrow();
});
});
@@ -173,7 +220,10 @@ describe('LiveQuery definitions', () => {
if (typeof definition.env !== 'undefined') {
expect(typeof definition.env).toBe('string');
}
expect(typeof definition.help).toBe('string');
expect(typeof definition.help).toBe(
'string',
`help for ${key} should be a string`
);
if (typeof definition.required !== 'undefined') {
expect(typeof definition.required).toBe('boolean');
}
@@ -183,3 +233,84 @@ describe('LiveQuery definitions', () => {
}
});
});
describe('execution', () => {
const binPath = path.resolve(__dirname, '../bin/parse-server');
let childProcess;
afterEach(async () => {
if (childProcess) {
childProcess.kill();
}
});
it('shoud start Parse Server', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
]);
childProcess.stdout.on('data', data => {
data = data.toString();
if (data.includes('parse-server running on')) {
done();
}
});
childProcess.stderr.on('data', data => {
done.fail(data.toString());
});
});
it('shoud start Parse Server with GraphQL', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--mountGraphQL',
]);
let output = '';
childProcess.stdout.on('data', data => {
data = data.toString();
output += data;
if (data.includes('GraphQL running on')) {
expect(output).toMatch('parse-server running on');
done();
}
});
childProcess.stderr.on('data', data => {
done.fail(data.toString());
});
});
it('shoud start Parse Server with GraphQL and Playground', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--mountGraphQL',
'--mountPlayground',
]);
let output = '';
childProcess.stdout.on('data', data => {
data = data.toString();
output += data;
if (data.includes('Playground running on')) {
expect(output).toMatch('GraphQL running on');
expect(output).toMatch('parse-server running on');
done();
}
});
childProcess.stderr.on('data', data => {
done.fail(data.toString());
});
});
});
+7 -10
View File
@@ -1,4 +1,5 @@
const CacheController = require('../src/Controllers/CacheController.js').default;
const CacheController = require('../lib/Controllers/CacheController.js')
.default;
describe('CacheController', function() {
let FakeCacheAdapter;
@@ -10,14 +11,13 @@ describe('CacheController', function() {
get: () => Promise.resolve(null),
put: jasmine.createSpy('put'),
del: jasmine.createSpy('del'),
clear: jasmine.createSpy('clear')
}
clear: jasmine.createSpy('clear'),
};
spyOn(FakeCacheAdapter, 'get').and.callThrough();
});
it('should expose role and user caches', (done) => {
it('should expose role and user caches', done => {
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
expect(cache.role).not.toEqual(null);
@@ -28,8 +28,7 @@ describe('CacheController', function() {
done();
});
['role', 'user'].forEach((cacheName) => {
['role', 'user'].forEach(cacheName => {
it('should prefix ' + cacheName + ' cache', () => {
const cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
@@ -60,8 +59,7 @@ describe('CacheController', function() {
expect(FakeCacheAdapter.clear.calls.count()).toEqual(3);
});
it('should handle cache rejections', (done) => {
it('should handle cache rejections', done => {
FakeCacheAdapter.get = () => Promise.reject();
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
@@ -70,5 +68,4 @@ describe('CacheController', function() {
fail('Promise should not be rejected.');
});
});
});
+45 -39
View File
@@ -1,5 +1,6 @@
const Client = require('../src/LiveQuery/Client').Client;
const ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
const Client = require('../lib/LiveQuery/Client').Client;
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer')
.ParseWebSocket;
describe('Client', function() {
it('can be initialized', function() {
@@ -13,7 +14,7 @@ describe('Client', function() {
it('can push response', function() {
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
Client.pushResponse(parseWebSocket, 'message');
@@ -22,7 +23,7 @@ describe('Client', function() {
it('can push error', function() {
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
Client.pushError(parseWebSocket, 1, 'error', true);
@@ -37,10 +38,10 @@ describe('Client', function() {
it('can add subscription information', function() {
const subscription = {};
const fields = ['test'];
const subscriptionInfo = {
const subscriptionInfo = {
subscription: subscription,
fields: fields
}
fields: fields,
};
const client = new Client(1, {});
client.addSubscriptionInfo(1, subscriptionInfo);
@@ -51,10 +52,10 @@ describe('Client', function() {
it('can get subscription information', function() {
const subscription = {};
const fields = ['test'];
const subscriptionInfo = {
const subscriptionInfo = {
subscription: subscription,
fields: fields
}
fields: fields,
};
const client = new Client(1, {});
client.addSubscriptionInfo(1, subscriptionInfo);
const subscriptionInfoAgain = client.getSubscriptionInfo(1);
@@ -65,10 +66,10 @@ describe('Client', function() {
it('can delete subscription information', function() {
const subscription = {};
const fields = ['test'];
const subscriptionInfo = {
const subscriptionInfo = {
subscription: subscription,
fields: fields
}
fields: fields,
};
const client = new Client(1, {});
client.addSubscriptionInfo(1, subscriptionInfo);
client.deleteSubscriptionInfo(1);
@@ -76,10 +77,9 @@ describe('Client', function() {
expect(client.subscriptionInfos.size).toBe(0);
});
it('can generate ParseObject JSON with null selected field', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
@@ -88,12 +88,14 @@ describe('Client', function() {
};
const client = new Client(1, {});
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(parseObjectJSON);
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(
parseObjectJSON
);
});
it('can generate ParseObject JSON with undefined selected field', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
@@ -102,18 +104,20 @@ describe('Client', function() {
};
const client = new Client(1, {});
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(parseObjectJSON);
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(
parseObjectJSON
);
});
it('can generate ParseObject JSON with selected fields', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
};
const client = new Client(1, {});
@@ -123,22 +127,24 @@ describe('Client', function() {
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
});
});
it('can generate ParseObject JSON with nonexistent selected fields', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
};
const client = new Client(1, {});
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, ['name']);
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, [
'name',
]);
expect(limitedParseObject).toEqual({
className: 'test',
@@ -152,7 +158,7 @@ describe('Client', function() {
it('can push connect response', function() {
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushConnect();
@@ -165,7 +171,7 @@ describe('Client', function() {
it('can push subscribe response', function() {
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushSubscribe(2);
@@ -179,7 +185,7 @@ describe('Client', function() {
it('can push unsubscribe response', function() {
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushUnsubscribe(2);
@@ -193,16 +199,16 @@ describe('Client', function() {
it('can push create response', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
};
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushCreate(2, parseObjectJSON);
@@ -217,16 +223,16 @@ describe('Client', function() {
it('can push enter response', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
};
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushEnter(2, parseObjectJSON);
@@ -241,16 +247,16 @@ describe('Client', function() {
it('can push update response', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
};
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushUpdate(2, parseObjectJSON);
@@ -265,16 +271,16 @@ describe('Client', function() {
it('can push leave response', function() {
const parseObjectJSON = {
key : 'value',
key: 'value',
className: 'test',
objectId: 'test',
updatedAt: '2015-12-07T21:27:13.746Z',
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
test: 'test'
test: 'test',
};
const parseWebSocket = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const client = new Client(1, parseWebSocket);
client.pushLeave(2, parseObjectJSON);
+27 -19
View File
@@ -1,41 +1,49 @@
const ClientSDK = require('../src/ClientSDK');
const ClientSDK = require('../lib/ClientSDK');
describe('ClientSDK', () => {
it('should properly parse the SDK versions', () => {
const clientSDKFromVersion = ClientSDK.fromString;
expect(clientSDKFromVersion('i1.1.1')).toEqual({
sdk: 'i',
version: '1.1.1'
version: '1.1.1',
});
expect(clientSDKFromVersion('i1')).toEqual({
sdk: 'i',
version: '1'
version: '1',
});
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
sdk: 'apple-tv',
version: '1.13.0'
version: '1.13.0',
});
expect(clientSDKFromVersion('js1.9.0')).toEqual({
sdk: 'js',
version: '1.9.0'
version: '1.9.0',
});
});
it('should properly sastisfy', () => {
expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js1.9.0")).toBe(true);
expect(
ClientSDK.compatible({
js: '>=1.9.0',
})('js1.9.0')
).toBe(true);
expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js2.0.0")).toBe(true);
expect(
ClientSDK.compatible({
js: '>=1.9.0',
})('js2.0.0')
).toBe(true);
expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js1.8.0")).toBe(false);
expect(
ClientSDK.compatible({
js: '>=1.9.0',
})('js1.8.0')
).toBe(false);
expect(ClientSDK.compatible({
js: '>=1.9.0'
})(undefined)).toBe(true);
})
})
expect(
ClientSDK.compatible({
js: '>=1.9.0',
})(undefined)
).toBe(true);
});
});
+1447 -881
View File
File diff suppressed because it is too large Load Diff
+157 -127
View File
@@ -1,105 +1,119 @@
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const LoggerController = require('../lib/Controllers/LoggerController')
.LoggerController;
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
.WinstonLoggerAdapter;
const fs = require('fs');
const Config = require('../lib/Config');
const loremFile = __dirname + '/support/lorem.txt';
describe("Cloud Code Logger", () => {
describe('Cloud Code Logger', () => {
let user;
beforeEach(done => {
let spy;
beforeEach(async () => {
Parse.User.enableUnsafeCurrentUser();
return reconfigureServer({
// useful to flip to false for fine tuning :).
silent: true,
}).then(() => {
return Parse.User.signUp('tester', 'abc')
.then(loggedInUser => user = loggedInUser)
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
.then(() => done())
});
})
.then(() => {
return Parse.User.signUp('tester', 'abc')
.catch(() => {})
.then(loggedInUser => (user = loggedInUser))
.then(() => Parse.User.logIn(user.get('username'), 'abc'));
})
.then(() => {
spy = spyOn(
Config.get('test').loggerController.adapter,
'log'
).and.callThrough();
});
});
// Note that helpers takes care of logout.
// see helpers.js:afterEach
it("should expose log to functions", done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define("loggerTest", (req, res) => {
it('should expose log to functions', () => {
const spy = spyOn(
Config.get('test').loggerController,
'log'
).and.callThrough();
Parse.Cloud.define('loggerTest', req => {
req.log.info('logTest', 'info log', { info: 'some log' });
req.log.error('logTest', 'error log', { error: 'there was an error' });
res.success({});
return {};
});
Parse.Cloud.run('loggerTest').then(() => {
return logController.getLogs({ from: Date.now() - 500, size: 1000 });
}).then((res) => {
expect(res.length).not.toBe(0);
const lastLogs = res.slice(0, 3);
const cloudFunctionMessage = lastLogs[0];
const errorMessage = lastLogs[1];
const infoMessage = lastLogs[2];
expect(cloudFunctionMessage.level).toBe('info');
expect(cloudFunctionMessage.params).toEqual({});
expect(cloudFunctionMessage.message).toMatch(/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/);
expect(cloudFunctionMessage.functionName).toEqual('loggerTest');
expect(errorMessage.level).toBe('error');
expect(errorMessage.error).toBe('there was an error');
expect(errorMessage.message).toBe('logTest error log');
expect(infoMessage.level).toBe('info');
expect(infoMessage.info).toBe('some log');
expect(infoMessage.message).toBe('logTest info log');
done();
return Parse.Cloud.run('loggerTest').then(() => {
expect(spy).toHaveBeenCalledTimes(3);
const cloudFunctionMessage = spy.calls.all()[2];
const errorMessage = spy.calls.all()[1];
const infoMessage = spy.calls.all()[0];
expect(cloudFunctionMessage.args[0]).toBe('info');
expect(cloudFunctionMessage.args[1][1].params).toEqual({});
expect(cloudFunctionMessage.args[1][0]).toMatch(
/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/
);
expect(cloudFunctionMessage.args[1][1].functionName).toEqual(
'loggerTest'
);
expect(errorMessage.args[0]).toBe('error');
expect(errorMessage.args[1][2].error).toBe('there was an error');
expect(errorMessage.args[1][0]).toBe('logTest');
expect(errorMessage.args[1][1]).toBe('error log');
expect(infoMessage.args[0]).toBe('info');
expect(infoMessage.args[1][2].info).toBe('some log');
expect(infoMessage.args[1][0]).toBe('logTest');
expect(infoMessage.args[1][1]).toBe('info log');
});
});
it('trigger should obfuscate password', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
res.success(req.object);
Parse.Cloud.beforeSave(Parse.User, req => {
return req.object;
});
Parse.User.signUp('tester123', 'abc')
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then((res) => {
const entry = res[0];
expect(entry.message).not.toMatch(/password":"abc/);
expect(entry.message).toMatch(/\*\*\*\*\*\*\*\*/);
.then(() => {
const entry = spy.calls.mostRecent().args;
expect(entry[1]).not.toMatch(/password":"abc/);
expect(entry[1]).toMatch(/\*\*\*\*\*\*\*\*/);
done();
})
.then(null, e => done.fail(e));
});
it("should expose log to trigger", (done) => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave("MyObject", (req, res) => {
it('should expose log to trigger', done => {
Parse.Cloud.beforeSave('MyObject', req => {
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
res.success({});
req.log.error('beforeSave MyObject', 'error log', {
error: 'there was an error',
});
return {};
});
const obj = new Parse.Object('MyObject');
obj.save().then(() => {
return logController.getLogs({ from: Date.now() - 500, size: 1000 })
}).then((res) => {
expect(res.length).not.toBe(0);
const lastLogs = res.slice(0, 3);
const cloudTriggerMessage = lastLogs[0];
const errorMessage = lastLogs[1];
const infoMessage = lastLogs[2];
expect(cloudTriggerMessage.level).toBe('info');
expect(cloudTriggerMessage.triggerType).toEqual('beforeSave');
expect(cloudTriggerMessage.message).toMatch(/beforeSave triggered for MyObject for user [^ ]*\n {2}Input: {}\n {2}Result: {}/);
expect(cloudTriggerMessage.user).toBe(user.id);
expect(errorMessage.level).toBe('error');
expect(errorMessage.error).toBe('there was an error');
expect(errorMessage.message).toBe('beforeSave MyObject error log');
expect(infoMessage.level).toBe('info');
expect(infoMessage.info).toBe('some log');
expect(infoMessage.message).toBe('beforeSave MyObject info log');
const lastCalls = spy.calls.all().reverse();
const cloudTriggerMessage = lastCalls[0].args;
const errorMessage = lastCalls[1].args;
const infoMessage = lastCalls[2].args;
expect(cloudTriggerMessage[0]).toBe('info');
expect(cloudTriggerMessage[2].triggerType).toEqual('beforeSave');
expect(cloudTriggerMessage[1]).toMatch(
/beforeSave triggered for MyObject for user [^ ]*\n {2}Input: {}\n {2}Result: {"object":{}}/
);
expect(cloudTriggerMessage[2].user).toBe(user.id);
expect(errorMessage[0]).toBe('error');
expect(errorMessage[3].error).toBe('there was an error');
expect(errorMessage[1] + ' ' + errorMessage[2]).toBe(
'beforeSave MyObject error log'
);
expect(infoMessage[0]).toBe('info');
expect(infoMessage[3].info).toBe('some log');
expect(infoMessage[1] + ' ' + infoMessage[2]).toBe(
'beforeSave MyObject info log'
);
done();
});
});
@@ -112,43 +126,39 @@ describe("Cloud Code Logger", () => {
});
it('should truncate input and result of long lines', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
const longString = fs.readFileSync(loremFile, 'utf8');
Parse.Cloud.define('aFunction', (req, res) => {
res.success(req.params);
Parse.Cloud.define('aFunction', req => {
return req.params;
});
Parse.Cloud.run('aFunction', { longString })
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(logs => {
const log = logs[0];
expect(log.level).toEqual('info');
expect(log.message).toMatch(
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m);
.then(() => {
const log = spy.calls.mostRecent().args;
expect(log[0]).toEqual('info');
expect(log[1]).toMatch(
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m
);
done();
})
.then(null, e => done.fail(e));
});
it('should log an afterSave', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.afterSave("MyObject", () => { });
Parse.Cloud.afterSave('MyObject', () => {});
new Parse.Object('MyObject')
.save()
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then((logs) => {
const log = logs[0];
expect(log.triggerType).toEqual('afterSave');
.then(() => {
const log = spy.calls.mostRecent().args;
expect(log[2].triggerType).toEqual('afterSave');
done();
})
// catch errors - not that the error is actually useful :(
// catch errors - not that the error is actually useful :(
.then(null, e => done.fail(e));
});
it('should log a denied beforeSave', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave("MyObject", (req, res) => {
res.error('uh oh!');
Parse.Cloud.beforeSave('MyObject', () => {
throw 'uh oh!';
});
new Parse.Object('MyObject')
@@ -157,58 +167,64 @@ describe("Cloud Code Logger", () => {
() => done.fail('this is not supposed to succeed'),
() => new Promise(resolve => setTimeout(resolve, 100))
)
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(logs => {
const log = logs[1]; // 0 is the 'uh oh!' from rejection...
expect(log.level).toEqual('error');
expect(log.error).toEqual({ code: 141, message: 'uh oh!' });
done()
.then(() => {
const logs = spy.calls.all().reverse();
const log = logs[1].args; // 0 is the 'uh oh!' from rejection...
expect(log[0]).toEqual('error');
const error = log[2].error;
expect(error instanceof Parse.Error).toBeTruthy();
expect(error.code).toBe(141);
expect(error.message).toBe('uh oh!');
done();
});
});
it('should log cloud function success', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define('aFunction', (req, res) => {
res.success('it worked!');
Parse.Cloud.define('aFunction', () => {
return 'it worked!';
});
Parse.Cloud.run('aFunction', { foo: 'bar' })
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(logs => {
const log = logs[0];
expect(log.level).toEqual('info');
expect(log.message).toMatch(
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Result: "it worked!/);
done();
});
Parse.Cloud.run('aFunction', { foo: 'bar' }).then(() => {
const log = spy.calls.mostRecent().args;
expect(log[0]).toEqual('info');
expect(log[1]).toMatch(
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Result: "it worked!/
);
done();
});
});
it('should log cloud function failure', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define('aFunction', (req, res) => {
res.error('it failed!');
Parse.Cloud.define('aFunction', () => {
throw 'it failed!';
});
Parse.Cloud.run('aFunction', { foo: 'bar' })
.then(null, () => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(logs => {
const log = logs[2];
expect(log.level).toEqual('error');
expect(log.message).toMatch(
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error: {"code":141,"message":"it failed!"}/);
.catch(() => {})
.then(() => {
const logs = spy.calls.all().reverse();
expect(logs[0].args[1]).toBe('Parse error: ');
expect(logs[0].args[2].message).toBe('it failed!');
const log = logs[1].args;
expect(log[0]).toEqual('error');
expect(log[1]).toMatch(
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error:/
);
const errorString = JSON.stringify(new Parse.Error(141, 'it failed!'));
expect(log[1].indexOf(errorString)).toBeGreaterThan(0);
done();
});
})
.catch(done.fail);
});
xit('should log a changed beforeSave indicating a change', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave("MyObject", (req, res) => {
Parse.Cloud.beforeSave('MyObject', req => {
const myObj = req.object;
myObj.set('aChange', true);
res.success(myObj);
return myObj;
});
new Parse.Object('MyObject')
@@ -228,19 +244,33 @@ describe("Cloud Code Logger", () => {
}).pend('needs more work.....');
it('cloud function should obfuscate password', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define('testFunction', (req, res) => {
res.success(1002,'verify code success');
Parse.Cloud.define('testFunction', () => {
return 'verify code success';
});
Parse.Cloud.run('testFunction', {username:'hawk',password:'123456'})
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then((res) => {
const entry = res[0];
expect(entry.params.password).toMatch(/\*\*\*\*\*\*\*\*/);
Parse.Cloud.run('testFunction', { username: 'hawk', password: '123456' })
.then(() => {
const entry = spy.calls.mostRecent().args;
expect(entry[2].params.password).toMatch(/\*\*\*\*\*\*\*\*/);
done();
})
.then(null, e => done.fail(e));
});
it('should only log once for object not found', async () => {
const config = Config.get('test');
const spy = spyOn(config.loggerController, 'error').and.callThrough();
try {
const object = new Parse.Object('Object');
object.id = 'invalid';
await object.fetch();
} catch (e) {
/**/
}
expect(spy).toHaveBeenCalled();
expect(spy.calls.count()).toBe(1);
const { args } = spy.calls.mostRecent();
expect(args[0]).toBe('Parse error: ');
expect(args[1].message).toBe('Object not found.');
});
});
+108 -39
View File
@@ -1,51 +1,120 @@
const DatabaseController = require('../src/Controllers/DatabaseController.js');
const DatabaseController = require('../lib/Controllers/DatabaseController.js');
const validateQuery = DatabaseController._validateQuery;
describe('DatabaseController', function() {
describe('validateQuery', function() {
describe('with skipMongoDBServer13732Workaround disabled (the default)', function() {
it('should restructure simple cases of SERVER-13732', done => {
const query = {
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
};
validateQuery(query, false);
expect(query).toEqual({
$or: [
{ a: 1, _rperm: { $in: ['a', 'b'] }, foo: 3 },
{ a: 2, _rperm: { $in: ['a', 'b'] }, foo: 3 },
],
});
done();
});
it('should restructure simple cases of SERVER-13732', (done) => {
const query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
done();
it('should not restructure SERVER-13732 queries with $nears', done => {
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
validateQuery(query, false);
expect(query).toEqual({
$or: [{ a: 1 }, { b: 1 }],
c: { $nearSphere: {} },
});
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
validateQuery(query, false);
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
done();
});
it('should push refactored keys down a tree for SERVER-13732', done => {
const query = {
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
};
validateQuery(query, false);
expect(query).toEqual({
$or: [
{ $or: [{ b: 1, a: 1 }, { b: 2, a: 1 }] },
{ $or: [{ c: 1, a: 1 }, { c: 2, a: 1 }] },
],
});
done();
});
it('should reject invalid queries', done => {
expect(() => validateQuery({ $or: { a: 1 } }, false)).toThrow();
done();
});
it('should accept valid queries', done => {
expect(() =>
validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, false)
).not.toThrow();
done();
});
});
it('should not restructure SERVER-13732 queries with $nears', (done) => {
let query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}});
describe('with skipMongoDBServer13732Workaround enabled', function() {
it('should not restructure simple cases of SERVER-13732', done => {
const query = {
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
};
validateQuery(query, true);
expect(query).toEqual({
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
});
done();
});
query = {$or: [{a: 1}, {b: 1}], c: {$near: {}}};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$near: {}}});
it('should not restructure SERVER-13732 queries with $nears', done => {
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
validateQuery(query, true);
expect(query).toEqual({
$or: [{ a: 1 }, { b: 1 }],
c: { $nearSphere: {} },
});
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
validateQuery(query, true);
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
done();
});
done();
it('should not push refactored keys down a tree for SERVER-13732', done => {
const query = {
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
};
validateQuery(query, true);
expect(query).toEqual({
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
});
done();
});
it('should reject invalid queries', done => {
expect(() => validateQuery({ $or: { a: 1 } }, true)).toThrow();
done();
});
it('should accept valid queries', done => {
expect(() =>
validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, true)
).not.toThrow();
done();
});
});
it('should push refactored keys down a tree for SERVER-13732', (done) => {
const query = {a: 1, $or: [{$or: [{b: 1}, {b: 2}]},
{$or: [{c: 1}, {c: 2}]}]};
validateQuery(query);
expect(query).toEqual({$or: [{$or: [{b: 1, a: 1}, {b: 2, a: 1}]},
{$or: [{c: 1, a: 1}, {c: 2, a: 1}]}]});
done();
});
it('should reject invalid queries', (done) => {
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
done();
});
it('should accept valid queries', (done) => {
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
done();
});
});
});
+302 -247
View File
@@ -1,11 +1,9 @@
"use strict";
'use strict';
const request = require('request');
const requestp = require('request-promise');
const Config = require('../src/Config');
describe("Email Verification Token Expiration: ", () => {
const Config = require('../lib/Config');
const request = require('../lib/request');
describe('Email Verification Token Expiration: ', () => {
it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
const user = new Parse.User();
let sendEmailOptions;
@@ -14,34 +12,40 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
// wait for 1 second - simulate user behavior to some extent
})
.then(() => {
// wait for 1 second - simulate user behavior to some extent
setTimeout(() => {
expect(sendEmailOptions).not.toBeUndefined();
console.log(sendEmailOptions.link);
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
expect(response.text).toEqual(
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'
);
done();
});
}, 1000);
}).catch((err) => {
})
.catch(err => {
jfail(err);
done();
});
@@ -55,41 +59,45 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
// wait for 1 second - simulate user behavior to some extent
})
.then(() => {
// wait for 1 second - simulate user behavior to some extent
setTimeout(() => {
expect(sendEmailOptions).not.toBeUndefined();
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
user.fetch()
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
user
.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
done();
})
.catch(() => {
.catch(error => {
jfail(error);
done();
});
});
}, 1000);
}).catch((error) => {
})
.catch(error => {
jfail(error);
done();
});
@@ -103,29 +111,34 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
})
.then(() => {
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
expect(response.text).toEqual(
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'
);
done();
});
}).catch((error) => {
})
.catch(error => {
jfail(error);
done();
});
@@ -139,36 +152,40 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
user.fetch()
})
.then(() => {
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
user
.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(true);
done();
})
.catch((error) => {
.catch(error => {
jfail(error);
done();
});
});
}).catch((error) => {
})
.catch(error => {
jfail(error);
done();
});
@@ -182,37 +199,40 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
})
.then(() => {
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken')
.then(user => {
expect(typeof user).toBe('object');
expect(user.get('emailVerified')).toBe(true);
done();
})
.catch((error) => {
.catch(error => {
jfail(error);
done();
});
});
}).catch((error) => {
})
.catch(error => {
jfail(error);
done();
});
@@ -226,14 +246,14 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername('sets_email_verify_token_expires_at');
@@ -243,7 +263,9 @@ describe("Email Verification Token Expiration: ", () => {
})
.then(() => {
const config = Config.get('test');
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
return config.database.find('_User', {
username: 'sets_email_verify_token_expires_at',
});
})
.then(results => {
expect(results.length).toBe(1);
@@ -269,36 +291,43 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("unsets_email_verify_token_expires_at");
user.setPassword("expiringToken");
user.setUsername('unsets_email_verify_token_expires_at');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
const config = Config.get('test');
return config.database.find('_User', {username: 'unsets_email_verify_token_expires_at'}).then((results) => {
expect(results.length).toBe(1);
return results[0];
})
return config.database
.find('_User', {
username: 'unsets_email_verify_token_expires_at',
})
.then(results => {
expect(results.length).toBe(1);
return results[0];
})
.then(user => {
expect(typeof user).toBe('object');
expect(user.emailVerified).toEqual(true);
expect(typeof user._email_verify_token).toBe('undefined');
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
expect(typeof user._email_verify_token_expires_at).toBe(
'undefined'
);
done();
})
.catch(error => {
@@ -321,31 +350,30 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
const serverConfig = {
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
};
// setup server WITHOUT enabling the expire email verify token flag
reconfigureServer(serverConfig)
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
return new Promise((resolve, reject) => {
request.get(sendEmailOptions.link, { followRedirect: false, })
.on('error', error => reject(error))
.on('response', (response) => {
expect(response.statusCode).toEqual(302);
resolve(user.fetch());
});
return request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
return user.fetch();
});
})
.then(() => {
@@ -355,15 +383,18 @@ describe("Email Verification Token Expiration: ", () => {
return reconfigureServer(serverConfig);
})
.then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
expect(response.text).toEqual(
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'
);
done();
});
})
.catch((error) => {
.catch(error => {
jfail(error);
done();
});
@@ -377,25 +408,25 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
const serverConfig = {
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
};
// setup server WITHOUT enabling the expire email verify token flag
reconfigureServer(serverConfig)
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
// just get the user again - DO NOT email verify the user
// just get the user again - DO NOT email verify the user
return user.fetch();
})
.then(() => {
@@ -405,22 +436,24 @@ describe("Email Verification Token Expiration: ", () => {
return reconfigureServer(serverConfig);
})
.then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
expect(response.text).toEqual(
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'
);
done();
});
})
.catch((error) => {
.catch(error => {
jfail(error);
done();
});
});
it('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', done => {
const user = new Parse.User();
let userBeforeEmailReset;
@@ -430,28 +463,30 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
sendMail: () => {},
};
const serverConfig = {
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
};
reconfigureServer(serverConfig)
.then(() => {
user.setUsername("newEmailVerifyTokenOnEmailReset");
user.setPassword("expiringToken");
user.setUsername('newEmailVerifyTokenOnEmailReset');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
const config = Config.get('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
});
return config.database
.find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
.then(results => {
return results[0];
});
})
.then(userFromDb => {
expect(typeof userFromDb).toBe('object');
@@ -459,25 +494,31 @@ describe("Email Verification Token Expiration: ", () => {
// trigger another token generation by setting the email
user.set('email', 'user@parse.com');
return new Promise((resolve) => {
// wait for half a sec to get a new expiration time
return new Promise(resolve => {
// wait for half a sec to get a new expiration time
setTimeout(() => resolve(user.save()), 500);
});
})
.then(() => {
const config = Config.get('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
});
return config.database
.find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
.then(results => {
return results[0];
});
})
.then(userAfterEmailReset => {
expect(typeof userAfterEmailReset).toBe('object');
expect(userBeforeEmailReset._email_verify_token).not.toEqual(userAfterEmailReset._email_verify_token);
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(userAfterEmailReset.__email_verify_token_expires_at);
expect(userBeforeEmailReset._email_verify_token).not.toEqual(
userAfterEmailReset._email_verify_token
);
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(
userAfterEmailReset.__email_verify_token_expires_at
);
expect(sendEmailOptions).toBeDefined();
done();
})
.catch((error) => {
.catch(error => {
jfail(error);
done();
});
@@ -494,14 +535,14 @@ describe("Email Verification Token Expiration: ", () => {
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername('resends_verification_token');
@@ -511,46 +552,53 @@ describe("Email Verification Token Expiration: ", () => {
})
.then(() => {
const config = Config.get('test');
return config.database.find('_User', {username: 'resends_verification_token'}).then((results) => {
return results[0];
});
return config.database
.find('_User', { username: 'resends_verification_token' })
.then(results => {
return results[0];
});
})
.then((newUser) => {
.then(newUser => {
// store this user before we make our email request
userBeforeRequest = newUser;
expect(sendVerificationEmailCallCount).toBe(1);
return requestp.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
return request({
url: 'http://localhost:8378/1/verificationEmailRequest',
method: 'POST',
body: {
email: 'user@parse.com'
email: 'user@parse.com',
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
json: true,
resolveWithFullResponse: true,
simple: false // this promise is only rejected if the call itself failed
});
})
.then((response) => {
expect(response.statusCode).toBe(200);
.then(response => {
expect(response.status).toBe(200);
expect(sendVerificationEmailCallCount).toBe(2);
expect(sendEmailOptions).toBeDefined();
// query for this user again
const config = Config.get('test');
return config.database.find('_User', {username: 'resends_verification_token'}).then((results) => {
return results[0];
});
return config.database
.find('_User', { username: 'resends_verification_token' })
.then(results => {
return results[0];
});
})
.then((userAfterRequest) => {
.then(userAfterRequest => {
// verify that our token & expiration has been changed for this new request
expect(typeof userAfterRequest).toBe('object');
expect(userBeforeRequest._email_verify_token).not.toEqual(userAfterRequest._email_verify_token);
expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(userAfterRequest.__email_verify_token_expires_at);
expect(userBeforeRequest._email_verify_token).not.toEqual(
userAfterRequest._email_verify_token
);
expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(
userAfterRequest.__email_verify_token_expires_at
);
done();
})
.catch(error => {
@@ -569,14 +617,14 @@ describe("Email Verification Token Expiration: ", () => {
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername('no_new_verification_token_once_verified');
@@ -585,34 +633,31 @@ describe("Email Verification Token Expiration: ", () => {
return user.signUp();
})
.then(() => {
return requestp.get({
return request({
url: sendEmailOptions.link,
followRedirect: false,
resolveWithFullResponse: true,
simple: false
})
.then((response) => {
expect(response.statusCode).toEqual(302);
});
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
});
})
.then(() => {
expect(sendVerificationEmailCallCount).toBe(1);
return requestp.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
return request({
url: 'http://localhost:8378/1/verificationEmailRequest',
method: 'POST',
body: {
email: 'user@parse.com'
email: 'user@parse.com',
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
json: true,
resolveWithFullResponse: true,
simple: false // this promise is only rejected if the call itself failed
})
.then((response) => {
expect(response.statusCode).toBe(400);
.then(fail, res => res)
.then(response => {
expect(response.status).toBe(400);
expect(sendVerificationEmailCallCount).toBe(1);
done();
});
@@ -632,31 +677,32 @@ describe("Email Verification Token Expiration: ", () => {
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
return requestp.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
return request({
url: 'http://localhost:8378/1/verificationEmailRequest',
method: 'POST',
body: {
email: 'user@parse.com'
email: 'user@parse.com',
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
json: true,
resolveWithFullResponse: true,
simple: false
})
.then(fail)
.catch(response => response)
.then(response => {
expect(response.statusCode).toBe(400);
expect(response.status).toBe(400);
expect(sendVerificationEmailCallCount).toBe(0);
expect(sendEmailOptions).not.toBeDefined();
done();
@@ -677,34 +723,35 @@ describe("Email Verification Token Expiration: ", () => {
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
request.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
request({
url: 'http://localhost:8378/1/verificationEmailRequest',
method: 'POST',
body: {},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
json: true,
resolveWithFullResponse: true,
simple: false
}, (err, response) => {
expect(response.statusCode).toBe(400);
expect(response.body.code).toBe(Parse.Error.EMAIL_MISSING);
expect(response.body.error).toBe('you must provide an email');
expect(sendVerificationEmailCallCount).toBe(0);
expect(sendEmailOptions).not.toBeDefined();
done();
});
})
.then(fail, response => response)
.then(response => {
expect(response.status).toBe(400);
expect(response.data.code).toBe(Parse.Error.EMAIL_MISSING);
expect(response.data.error).toBe('you must provide an email');
expect(sendVerificationEmailCallCount).toBe(0);
expect(sendEmailOptions).not.toBeDefined();
done();
});
})
.catch(error => {
jfail(error);
@@ -721,34 +768,37 @@ describe("Email Verification Token Expiration: ", () => {
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
request.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
body: {email: 3},
request({
url: 'http://localhost:8378/1/verificationEmailRequest',
method: 'POST',
body: { email: 3 },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
json: true,
resolveWithFullResponse: true,
simple: false
}, (err, response) => {
expect(response.statusCode).toBe(400);
expect(response.body.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
expect(response.body.error).toBe('you must provide a valid email string');
expect(sendVerificationEmailCallCount).toBe(0);
expect(sendEmailOptions).not.toBeDefined();
done();
});
})
.then(fail, res => res)
.then(response => {
expect(response.status).toBe(400);
expect(response.data.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
expect(response.data.error).toBe(
'you must provide a valid email string'
);
expect(sendVerificationEmailCallCount).toBe(0);
expect(sendEmailOptions).not.toBeDefined();
done();
});
})
.catch(error => {
jfail(error);
@@ -764,27 +814,29 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
user.fetch()
user
.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
expect(typeof user.get('_email_verify_token_expires_at')).toBe(
'undefined'
);
expect(sendEmailOptions).toBeDefined();
done();
})
@@ -792,8 +844,8 @@ describe("Email Verification Token Expiration: ", () => {
jfail(error);
done();
});
}).catch((error) => {
})
.catch(error => {
jfail(error);
done();
});
@@ -807,26 +859,28 @@ describe("Email Verification Token Expiration: ", () => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => { }
}
sendMail: () => {},
};
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.setUsername('testEmailVerifyTokenValidity');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
})
.then(() => {
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken')
.then(user => {
expect(typeof user).toBe('object');
expect(user.get('emailVerified')).toBe(true);
@@ -840,22 +894,23 @@ describe("Email Verification Token Expiration: ", () => {
expect(user.get('email')).toBe('newEmail@parse.com');
expect(user.get('emailVerified')).toBe(false);
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
request({
url: sendEmailOptions.link,
followRedirects: false,
}).then(response => {
expect(response.status).toEqual(302);
done();
});
})
.catch((error) => {
.catch(error => {
jfail(error);
done();
});
});
}).catch((error) => {
})
.catch(error => {
jfail(error);
done();
});
});
})
});
+63
View File
@@ -0,0 +1,63 @@
const ParseServer = require('../lib/index');
const express = require('express');
const request = require('../lib/request');
describe('Enable express error handler', () => {
it('should call the default handler in case of error, like updating a non existing object', done => {
const serverUrl = 'http://localhost:12667/parse';
const appId = 'anOtherTestApp';
const masterKey = 'anOtherTestMasterKey';
let server;
let lastError;
const parseServer = ParseServer.ParseServer(
Object.assign({}, defaultConfiguration, {
appId: appId,
masterKey: masterKey,
serverURL: serverUrl,
enableExpressErrorHandler: true,
serverStartComplete: () => {
expect(Parse.applicationId).toEqual('anOtherTestApp');
const app = express();
app.use('/parse', parseServer);
server = app.listen(12667);
app.use(function(err, req, res, next) {
next;
lastError = err;
});
request({
method: 'PUT',
url: serverUrl + '/classes/AnyClass/nonExistingId',
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
'Content-Type': 'application/json',
},
body: { someField: 'blablabla' },
})
.then(() => {
fail('Should throw error');
})
.catch(response => {
const reqError = response.data;
expect(reqError).toBeDefined();
expect(lastError).toBeDefined();
expect(lastError.code).toEqual(101);
expect(lastError.message).toEqual('Object not found.');
expect(lastError.code).toEqual(reqError.code);
expect(lastError.message).toEqual(reqError.error);
})
.then(() => {
server.close(done);
});
},
})
);
});
});
+41 -32
View File
@@ -1,46 +1,55 @@
const auth = require('../src/Auth');
const Config = require('../src/Config');
const rest = require('../src/rest');
const auth = require('../lib/Auth');
const Config = require('../lib/Config');
const rest = require('../lib/rest');
describe('Enable single schema cache', () => {
beforeEach((done) => {
beforeEach(done => {
reconfigureServer({
enableSingleSchemaCache: true,
schemaCacheTTL: 30000
schemaCacheTTL: 30000,
}).then(() => {
done();
});
});
it('can perform multiple create and query operations', (done) => {
it('can perform multiple create and query operations', done => {
let config = fakeRequestForConfig();
let nobody = auth.nobody(config);
rest.create(config, nobody, 'Foo', {type: 1}).then(() => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.create(config, nobody, 'Foo', {type: 2});
}).then(() => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.create(config, nobody, 'Bar');
}).then(() => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.find(config, nobody, 'Bar', {type: 1});
}).then(() => {
fail('Should throw error');
done();
}, (error) => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
expect(error).toBeDefined();
return rest.find(config, nobody, 'Foo', {type: 1});
}).then((response) => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
expect(response.results.length).toEqual(1);
done();
});
rest
.create(config, nobody, 'Foo', { type: 1 })
.then(() => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.create(config, nobody, 'Foo', { type: 2 });
})
.then(() => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.create(config, nobody, 'Bar');
})
.then(() => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.find(config, nobody, 'Bar', { type: 1 });
})
.then(
() => {
fail('Should throw error');
done();
},
error => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
expect(error).toBeDefined();
return rest.find(config, nobody, 'Foo', { type: 1 });
}
)
.then(response => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
expect(response.results.length).toEqual(1);
done();
});
});
});
+2 -1
View File
@@ -1,4 +1,5 @@
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
describe('EventEmitterPubSub', function() {
it('can publish and subscribe', function() {
+86 -30
View File
@@ -1,39 +1,43 @@
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
const Config = require("../src/Config");
const FilesController = require('../src/Controllers/FilesController').default;
const LoggerController = require('../lib/Controllers/LoggerController')
.LoggerController;
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
.WinstonLoggerAdapter;
const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter')
.GridFSBucketAdapter;
const Config = require('../lib/Config');
const FilesController = require('../lib/Controllers/FilesController').default;
const mockAdapter = {
createFile: () => {
return Parse.Promise.reject(new Error('it failed'));
return Promise.reject(new Error('it failed with xyz'));
},
deleteFile: () => { },
getFileData: () => { },
getFileLocation: () => 'xyz'
}
deleteFile: () => {},
getFileData: () => {},
getFileLocation: () => 'xyz',
};
// Small additional tests to improve overall coverage
describe("FilesController",() =>{
it("should properly expand objects", (done) => {
describe('FilesController', () => {
it('should properly expand objects', done => {
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
const filesController = new FilesController(gridStoreAdapter)
const result = filesController.expandFilesInObject(config, function(){});
const gridStoreAdapter = new GridFSBucketAdapter(
'mongodb://localhost:27017/parse'
);
const filesController = new FilesController(gridStoreAdapter);
const result = filesController.expandFilesInObject(config, function() {});
expect(result).toBeUndefined();
const fullFile = {
type: '__type',
url: "http://an.url"
}
url: 'http://an.url',
};
const anObject = {
aFile: fullFile
}
aFile: fullFile,
};
filesController.expandFilesInObject(config, anObject);
expect(anObject.aFile.url).toEqual("http://an.url");
expect(anObject.aFile.url).toEqual('http://an.url');
done();
});
@@ -42,24 +46,76 @@ describe("FilesController",() =>{
const logController = new LoggerController(new WinstonLoggerAdapter());
reconfigureServer({ filesAdapter: mockAdapter })
.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
.then(() => new Parse.File("yolo.txt", [1,2,3], "text/plain").save())
.then(() => new Parse.File('yolo.txt', [1, 2, 3], 'text/plain').save())
.then(
() => done.fail('should not succeed'),
() => setImmediate(() => Parse.Promise.as('done'))
() => setImmediate(() => Promise.resolve('done'))
)
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then((logs) => {
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
.then(() =>
logController.getLogs({ from: Date.now() - 1000, size: 1000 })
)
.then(logs => {
// we get two logs here: 1. the source of the failure to save the file
// and 2 the message that will be sent back to the client.
const log1 = logs.pop();
const log1 = logs.find(
x => x.message === 'Error creating a file: it failed with xyz'
);
expect(log1.level).toBe('error');
expect(log1.message).toBe('it failed');
const log2 = logs.pop();
const log2 = logs.find(
x => x.message === 'Parse error: Could not store file: yolo.txt.'
);
expect(log2.level).toBe('error');
expect(log2.code).toBe(130);
expect(log2.message).toBe('Could not store file.');
done();
});
});
it('should add a unique hash to the file name when the preserveFileName option is false', done => {
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridFSBucketAdapter(
'mongodb://localhost:27017/parse'
);
spyOn(gridStoreAdapter, 'createFile');
gridStoreAdapter.createFile.and.returnValue(Promise.resolve());
const fileName = 'randomFileName.pdf';
const regexEscapedFileName = fileName.replace(/\./g, '\\$&');
const filesController = new FilesController(gridStoreAdapter, null, {
preserveFileName: false,
});
filesController.createFile(config, fileName);
expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1);
expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toMatch(
`^.{32}_${regexEscapedFileName}$`
);
done();
});
it('should not add a unique hash to the file name when the preserveFileName option is true', done => {
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridFSBucketAdapter(
'mongodb://localhost:27017/parse'
);
spyOn(gridStoreAdapter, 'createFile');
gridStoreAdapter.createFile.and.returnValue(Promise.resolve());
const fileName = 'randomFileName.pdf';
const filesController = new FilesController(gridStoreAdapter, null, {
preserveFileName: true,
});
filesController.createFile(config, fileName);
expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1);
expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toEqual(
fileName
);
done();
});
});
+63
View File
@@ -0,0 +1,63 @@
const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter')
.GridStoreAdapter;
const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter')
.GridFSBucketAdapter;
const { randomString } = require('../lib/cryptoUtils');
const databaseURI = 'mongodb://localhost:27017/parse';
async function expectMissingFile(gfsAdapter, name) {
try {
await gfsAdapter.getFileData(name);
fail('should have thrown');
} catch (e) {
expect(e.message).toEqual('FileNotFound: file myFileName was not found');
}
}
describe('GridFSBucket and GridStore interop', () => {
beforeEach(async () => {
const gsAdapter = new GridStoreAdapter(databaseURI);
const db = await gsAdapter._connect();
db.dropDatabase();
});
it('a file created in GridStore should be available in GridFS', async () => {
const gsAdapter = new GridStoreAdapter(databaseURI);
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
await expectMissingFile(gfsAdapter, 'myFileName');
const originalString = 'abcdefghi';
await gsAdapter.createFile('myFileName', originalString);
const gsResult = await gsAdapter.getFileData('myFileName');
expect(gsResult.toString('utf8')).toBe(originalString);
const gfsResult = await gfsAdapter.getFileData('myFileName');
expect(gfsResult.toString('utf8')).toBe(originalString);
});
it('properly fetches a large file from GridFS', async () => {
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
const twoMegabytesFile = randomString(2048 * 1024);
await gfsAdapter.createFile('myFileName', twoMegabytesFile);
const gfsResult = await gfsAdapter.getFileData('myFileName');
expect(gfsResult.toString('utf8')).toBe(twoMegabytesFile);
});
it('properly deletes a file from GridFS', async () => {
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
await gfsAdapter.createFile('myFileName', 'a simple file');
await gfsAdapter.deleteFile('myFileName');
await expectMissingFile(gfsAdapter, 'myFileName');
}, 1000000);
it('properly overrides files', async () => {
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
await gfsAdapter.createFile('myFileName', 'a simple file');
await gfsAdapter.createFile('myFileName', 'an overrided simple file');
const data = await gfsAdapter.getFileData('myFileName');
expect(data.toString('utf8')).toBe('an overrided simple file');
const bucket = await gfsAdapter._getBucket();
const documents = await bucket.find({ filename: 'myFileName' }).toArray();
expect(documents.length).toBe(2);
await gfsAdapter.deleteFile('myFileName');
await expectMissingFile(gfsAdapter, 'myFileName');
});
});
-87
View File
@@ -1,87 +0,0 @@
const MongoClient = require("mongodb").MongoClient;
const GridStore = require("mongodb").GridStore;
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
const Config = require("../src/Config");
const FilesController = require('../src/Controllers/FilesController').default;
// Small additional tests to improve overall coverage
describe_only_db('mongo')("GridStoreAdapter",() =>{
it("should properly instanciate the GridStore when deleting a file", (done) => {
const databaseURI = 'mongodb://localhost:27017/parse';
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridStoreAdapter(databaseURI);
const filesController = new FilesController(gridStoreAdapter);
// save original unlink before redefinition
const originalUnlink = GridStore.prototype.unlink;
let gridStoreMode;
// new unlink method that will capture the mode in which GridStore was opened
GridStore.prototype.unlink = function() {
// restore original unlink during first call
GridStore.prototype.unlink = originalUnlink;
gridStoreMode = this.mode;
return originalUnlink.call(this);
};
filesController.createFile(config, 'myFilename.txt', 'my file content', 'text/plain')
.then(myFile => {
return MongoClient.connect(databaseURI)
.then(database => {
// Verify the existance of the fs.files document
return database.collection('fs.files').count().then(count => {
expect(count).toEqual(1);
return database;
});
})
.then(database => {
// Verify the existance of the fs.files document
return database.collection('fs.chunks').count().then(count => {
expect(count).toEqual(1);
return database.close();
});
})
.then(() => {
return filesController.deleteFile(config, myFile.name);
});
})
.then(() => {
return MongoClient.connect(databaseURI)
.then(database => {
// Verify the existance of the fs.files document
return database.collection('fs.files').count().then(count => {
expect(count).toEqual(0);
return database;
});
})
.then(database => {
// Verify the existance of the fs.files document
return database.collection('fs.chunks').count().then(count => {
expect(count).toEqual(0);
return database.close();
});
});
})
.then(() => {
// Verify that gridStore was opened in read only mode
expect(gridStoreMode).toEqual('r');
done();
})
.catch(fail);
})
});
+99
View File
@@ -0,0 +1,99 @@
const MongoClient = require('mongodb').MongoClient;
const GridStore = require('mongodb').GridStore;
const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter')
.GridStoreAdapter;
const Config = require('../lib/Config');
const FilesController = require('../lib/Controllers/FilesController').default;
// Small additional tests to improve overall coverage
describe_only_db('mongo')('GridStoreAdapter', () => {
it('should properly instanciate the GridStore when deleting a file', async done => {
const databaseURI = 'mongodb://localhost:27017/parse';
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridStoreAdapter(databaseURI);
const db = await gridStoreAdapter._connect();
db.dropDatabase();
const filesController = new FilesController(
gridStoreAdapter,
Parse.applicationId,
{}
);
// save original unlink before redefinition
const originalUnlink = GridStore.prototype.unlink;
let gridStoreMode;
// new unlink method that will capture the mode in which GridStore was opened
GridStore.prototype.unlink = function() {
// restore original unlink during first call
GridStore.prototype.unlink = originalUnlink;
gridStoreMode = this.mode;
return originalUnlink.call(this);
};
filesController
.createFile(config, 'myFilename.txt', 'my file content', 'text/plain')
.then(myFile => {
return MongoClient.connect(databaseURI)
.then(client => {
const database = client.db(client.s.options.dbName);
// Verify the existance of the fs.files document
return database
.collection('fs.files')
.count()
.then(count => {
expect(count).toEqual(1);
return { database, client };
});
})
.then(({ database, client }) => {
// Verify the existance of the fs.files document
return database
.collection('fs.chunks')
.count()
.then(count => {
expect(count).toEqual(1);
return client.close();
});
})
.then(() => {
return filesController.deleteFile(config, myFile.name);
});
})
.then(() => {
return MongoClient.connect(databaseURI)
.then(client => {
const database = client.db(client.s.options.dbName);
// Verify the existance of the fs.files document
return database
.collection('fs.files')
.count()
.then(count => {
expect(count).toEqual(0);
return { database, client };
});
})
.then(({ database, client }) => {
// Verify the existance of the fs.files document
return database
.collection('fs.chunks')
.count()
.then(count => {
expect(count).toEqual(0);
return client.close();
});
});
})
.then(() => {
// Verify that gridStore was opened in read only mode
expect(gridStoreMode).toEqual('r');
done();
})
.catch(fail);
});
});
+146 -179
View File
@@ -1,116 +1,97 @@
'use strict';
const httpRequest = require("../src/cloud-code/httpRequest"),
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
const httpRequest = require('../lib/cloud-code/httpRequest'),
HTTPResponse = require('../lib/cloud-code/HTTPResponse').default,
bodyParser = require('body-parser'),
express = require("express");
express = require('express');
const port = 13371;
const httpRequestServer = "http://localhost:" + port;
const httpRequestServer = 'http://localhost:' + port;
const app = express();
app.use(bodyParser.json({ 'type': '*/*' }));
app.get("/hello", function(req, res){
res.json({response: "OK"});
});
function startServer(done) {
const app = express();
app.use(bodyParser.json({ type: '*/*' }));
app.get('/hello', function(req, res) {
res.json({ response: 'OK' });
});
app.get("/404", function(req, res){
res.status(404);
res.send("NO");
});
app.get('/404', function(req, res) {
res.status(404);
res.send('NO');
});
app.get("/301", function(req, res){
res.status(301);
res.location("/hello");
res.send();
});
app.get('/301', function(req, res) {
res.status(301);
res.location('/hello');
res.send();
});
app.post('/echo', function(req, res){
res.json(req.body);
});
app.post('/echo', function(req, res) {
res.json(req.body);
});
app.get('/qs', function(req, res){
res.json(req.query);
});
app.get('/qs', function(req, res) {
res.json(req.query);
});
app.listen(13371);
return app.listen(13371, undefined, done);
}
describe('httpRequest', () => {
let server;
beforeAll(done => {
server = startServer(done);
});
describe("httpRequest", () => {
it("should do /hello", (done) => {
afterAll(done => {
server.close(done);
});
it('should do /hello', done => {
httpRequest({
url: httpRequestServer + "/hello"
}).then(function(httpResponse){
url: httpRequestServer + '/hello',
}).then(function(httpResponse) {
expect(httpResponse.status).toBe(200);
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
expect(httpResponse.text).toEqual('{"response":"OK"}');
expect(httpResponse.data.response).toEqual("OK");
expect(httpResponse.data.response).toEqual('OK');
done();
}, function(){
fail("should not fail");
done();
})
}, done.fail);
});
it("should do /hello with callback and promises", (done) => {
let calls = 0;
it('should do not follow redirects by default', done => {
httpRequest({
url: httpRequestServer + "/hello",
success: function() { calls++; },
error: function() { calls++; }
}).then(function(httpResponse){
expect(calls).toBe(1);
expect(httpResponse.status).toBe(200);
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
expect(httpResponse.text).toEqual('{"response":"OK"}');
expect(httpResponse.data.response).toEqual("OK");
done();
}, function(){
fail("should not fail");
done();
})
});
it("should do not follow redirects by default", (done) => {
httpRequest({
url: httpRequestServer + "/301"
}).then(function(httpResponse){
url: httpRequestServer + '/301',
}).then(function(httpResponse) {
expect(httpResponse.status).toBe(301);
done();
}, function(){
fail("should not fail");
done();
})
}, done.fail);
});
it("should follow redirects when set", (done) => {
it('should follow redirects when set', done => {
httpRequest({
url: httpRequestServer + "/301",
followRedirects: true
}).then(function(httpResponse){
url: httpRequestServer + '/301',
followRedirects: true,
}).then(function(httpResponse) {
expect(httpResponse.status).toBe(200);
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
expect(httpResponse.text).toEqual('{"response":"OK"}');
expect(httpResponse.data.response).toEqual("OK");
expect(httpResponse.data.response).toEqual('OK');
done();
}, function(){
fail("should not fail");
done();
})
}, done.fail);
});
it("should fail on 404", (done) => {
it('should fail on 404', done => {
let calls = 0;
httpRequest({
url: httpRequestServer + "/404",
success: function() {
url: httpRequestServer + '/404',
}).then(
function() {
calls++;
fail("should not succeed");
fail('should not succeed');
done();
},
error: function(httpResponse) {
function(httpResponse) {
calls++;
expect(calls).toBe(1);
expect(httpResponse.status).toBe(404);
@@ -119,131 +100,116 @@ describe("httpRequest", () => {
expect(httpResponse.data).toBe(undefined);
done();
}
});
})
it("should fail on 404", (done) => {
httpRequest({
url: httpRequestServer + "/404",
}).then(function(){
fail("should not succeed");
done();
}, function(httpResponse){
expect(httpResponse.status).toBe(404);
expect(httpResponse.buffer).toEqual(new Buffer('NO'));
expect(httpResponse.text).toEqual('NO');
expect(httpResponse.data).toBe(undefined);
done();
})
})
it("should post on echo", (done) => {
let calls = 0;
httpRequest({
method: "POST",
url: httpRequestServer + "/echo",
body: {
foo: "bar"
},
headers: {
'Content-Type': 'application/json'
},
success: function() { calls++; },
error: function() { calls++; }
}).then(function(httpResponse){
expect(calls).toBe(1);
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({foo: "bar"});
done();
}, function(){
fail("should not fail");
done();
})
);
});
it("should encode a query string body by default", (done) => {
it('should post on echo', done => {
httpRequest({
method: 'POST',
url: httpRequestServer + '/echo',
body: {
foo: 'bar',
},
headers: {
'Content-Type': 'application/json',
},
}).then(
function(httpResponse) {
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({ foo: 'bar' });
done();
},
function() {
fail('should not fail');
done();
}
);
});
it('should encode a query string body by default', done => {
const options = {
body: {"foo": "bar"},
}
body: { foo: 'bar' },
};
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual('foo=bar');
expect(result.headers['Content-Type']).toEqual('application/x-www-form-urlencoded');
expect(result.headers['Content-Type']).toEqual(
'application/x-www-form-urlencoded'
);
done();
});
})
it("should encode a JSON body", (done) => {
it('should encode a JSON body', done => {
const options = {
body: {"foo": "bar"},
headers: {'Content-Type': 'application/json'}
}
body: { foo: 'bar' },
headers: { 'Content-Type': 'application/json' },
};
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual('{"foo":"bar"}');
done();
})
it("should encode a www-form body", (done) => {
});
it('should encode a www-form body', done => {
const options = {
body: {"foo": "bar", "bar": "baz"},
headers: {'cOntent-tYpe': 'application/x-www-form-urlencoded'}
}
body: { foo: 'bar', bar: 'baz' },
headers: { 'cOntent-tYpe': 'application/x-www-form-urlencoded' },
};
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual("foo=bar&bar=baz");
expect(result.body).toEqual('foo=bar&bar=baz');
done();
});
it("should not encode a wrong content type", (done) => {
it('should not encode a wrong content type', done => {
const options = {
body:{"foo": "bar", "bar": "baz"},
headers: {'cOntent-tYpe': 'mime/jpeg'}
}
body: { foo: 'bar', bar: 'baz' },
headers: { 'cOntent-tYpe': 'mime/jpeg' },
};
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual({"foo": "bar", "bar": "baz"});
expect(result.body).toEqual({ foo: 'bar', bar: 'baz' });
done();
});
it("should fail gracefully", (done) => {
it('should fail gracefully', done => {
httpRequest({
url: "http://not a good url",
success: function() {
fail("should not succeed");
done();
},
error: function(error) {
expect(error).not.toBeUndefined();
expect(error).not.toBeNull();
done();
}
url: 'http://not a good url',
}).then(done.fail, function(error) {
expect(error).not.toBeUndefined();
expect(error).not.toBeNull();
done();
});
});
it("should params object to query string", (done) => {
it('should params object to query string', done => {
httpRequest({
url: httpRequestServer + "/qs",
url: httpRequestServer + '/qs',
params: {
foo: "bar"
foo: 'bar',
},
}).then(
function(httpResponse) {
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({ foo: 'bar' });
done();
},
function() {
fail('should not fail');
done();
}
}).then(function(httpResponse){
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({foo: "bar"});
done();
}, function(){
fail("should not fail");
done();
})
);
});
it("should params string to query string", (done) => {
it('should params string to query string', done => {
httpRequest({
url: httpRequestServer + "/qs",
params: "foo=bar&foo2=bar2"
}).then(function(httpResponse){
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({foo: "bar", foo2: 'bar2'});
done();
}, function(){
fail("should not fail");
done();
})
url: httpRequestServer + '/qs',
params: 'foo=bar&foo2=bar2',
}).then(
function(httpResponse) {
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({ foo: 'bar', foo2: 'bar2' });
done();
},
function() {
fail('should not fail');
done();
}
);
});
it('should not crash with undefined body', () => {
@@ -268,17 +234,17 @@ describe("httpRequest", () => {
});
it('serialized httpResponse correctly with body object', () => {
const httpResponse = new HTTPResponse({}, {foo: "bar"});
const httpResponse = new HTTPResponse({}, { foo: 'bar' });
Parse._encode(httpResponse);
const serialized = JSON.stringify(httpResponse);
const result = JSON.parse(serialized);
expect(httpResponse.text).toEqual('{"foo":"bar"}');
expect(httpResponse.data).toEqual({foo: 'bar'});
expect(httpResponse.body).toEqual({foo: 'bar'});
expect(httpResponse.data).toEqual({ foo: 'bar' });
expect(httpResponse.body).toEqual({ foo: 'bar' });
expect(result.text).toEqual('{"foo":"bar"}');
expect(result.data).toEqual({foo: 'bar'});
expect(result.data).toEqual({ foo: 'bar' });
expect(result.body).toEqual(undefined);
});
@@ -299,15 +265,17 @@ describe("httpRequest", () => {
const serialized = JSON.stringify(httpResponse);
const result = JSON.parse(serialized);
expect(result.text).toEqual('{"foo":"bar"}');
expect(result.data).toEqual({foo: 'bar'});
expect(result.data).toEqual({ foo: 'bar' });
});
it('serialized httpResponse with Parse._encode should be allright', () => {
const json = '{"foo":"bar"}';
const httpResponse = new HTTPResponse({}, new Buffer(json));
const encoded = Parse._encode(httpResponse);
let foundData, foundText, foundBody = false;
for(const key in encoded) {
let foundData,
foundText,
foundBody = false;
for (const key in encoded) {
if (key == 'data') {
foundData = true;
}
@@ -322,5 +290,4 @@ describe("httpRequest", () => {
expect(foundText).toBe(true);
expect(foundBody).toBe(false);
});
});
+9 -12
View File
@@ -1,26 +1,24 @@
const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
const InMemoryCache = require('../lib/Adapters/Cache/InMemoryCache').default;
describe('InMemoryCache', function() {
const BASE_TTL = {
ttl: 100
ttl: 100,
};
const NO_EXPIRE_TTL = {
ttl: NaN
ttl: NaN,
};
const KEY = 'hello';
const KEY_2 = KEY + '_2';
const VALUE = 'world';
function wait(sleep) {
return new Promise(function(resolve) {
setTimeout(resolve, sleep);
})
});
}
it('should destroy a expire items in the cache', (done) => {
it('should destroy a expire items in the cache', done => {
const cache = new InMemoryCache(BASE_TTL);
cache.put(KEY, VALUE);
@@ -28,14 +26,14 @@ describe('InMemoryCache', function() {
let value = cache.get(KEY);
expect(value).toEqual(VALUE);
wait(BASE_TTL.ttl * 2).then(() => {
value = cache.get(KEY)
wait(BASE_TTL.ttl * 10).then(() => {
value = cache.get(KEY);
expect(value).toEqual(null);
done();
});
});
it('should delete items', (done) => {
it('should delete items', done => {
const cache = new InMemoryCache(NO_EXPIRE_TTL);
cache.put(KEY, VALUE);
cache.put(KEY_2, VALUE);
@@ -52,7 +50,7 @@ describe('InMemoryCache', function() {
done();
});
it('should clear all items', (done) => {
it('should clear all items', done => {
const cache = new InMemoryCache(NO_EXPIRE_TTL);
cache.put(KEY, VALUE);
cache.put(KEY_2, VALUE);
@@ -70,5 +68,4 @@ describe('InMemoryCache', function() {
const cache = new InMemoryCache({});
expect(cache.ttl).toEqual(5 * 1000);
});
});
+19 -17
View File
@@ -1,4 +1,5 @@
const InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
.default;
describe('InMemoryCacheAdapter', function() {
const KEY = 'hello';
@@ -7,12 +8,12 @@ describe('InMemoryCacheAdapter', function() {
function wait(sleep) {
return new Promise(function(resolve) {
setTimeout(resolve, sleep);
})
});
}
it('should expose promisifyed methods', (done) => {
it('should expose promisifyed methods', done => {
const cache = new InMemoryCacheAdapter({
ttl: NaN
ttl: NaN,
});
// Verify all methods return promises.
@@ -20,38 +21,39 @@ describe('InMemoryCacheAdapter', function() {
cache.put(KEY, VALUE),
cache.del(KEY),
cache.get(KEY),
cache.clear()
cache.clear(),
]).then(() => {
done();
});
});
it('should get/set/clear', (done) => {
it('should get/set/clear', done => {
const cache = new InMemoryCacheAdapter({
ttl: NaN
ttl: NaN,
});
cache.put(KEY, VALUE)
cache
.put(KEY, VALUE)
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(VALUE))
.then(value => expect(value).toEqual(VALUE))
.then(() => cache.clear())
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(null))
.then(value => expect(value).toEqual(null))
.then(done);
});
it('should expire after ttl', (done) => {
it('should expire after ttl', done => {
const cache = new InMemoryCacheAdapter({
ttl: 10
ttl: 10,
});
cache.put(KEY, VALUE)
cache
.put(KEY, VALUE)
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(VALUE))
.then(value => expect(value).toEqual(VALUE))
.then(wait.bind(null, 50))
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(null))
.then(value => expect(value).toEqual(null))
.then(done);
})
});
});
+195 -75
View File
@@ -1,56 +1,71 @@
const auth = require('../src/Auth');
const Config = require('../src/Config');
const rest = require('../src/rest');
const InstallationsRouter = require('../src/Routers/InstallationsRouter').InstallationsRouter;
const auth = require('../lib/Auth');
const Config = require('../lib/Config');
const rest = require('../lib/rest');
const InstallationsRouter = require('../lib/Routers/InstallationsRouter')
.InstallationsRouter;
describe('InstallationsRouter', () => {
it('uses find condition from request.body', (done) => {
it('uses find condition from request.body', done => {
const config = Config.get('test');
const androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
};
const iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
};
const request = {
config: config,
auth: auth.master(config),
body: {
where: {
deviceType: 'android'
}
deviceType: 'android',
},
},
query: {},
info: {}
info: {},
};
const router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
rest
.create(
config,
auth.nobody(config),
'_Installation',
androidDeviceRequest
)
.then(() => {
return rest.create(
config,
auth.nobody(config),
'_Installation',
iosDeviceRequest
);
})
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
})
.then(res => {
const results = res.response.results;
expect(results.length).toEqual(1);
done();
}).catch((err) => {
})
.catch(err => {
fail(JSON.stringify(err));
done();
});
});
it('uses find condition from request.query', (done) => {
it('uses find condition from request.query', done => {
const config = Config.get('test');
const androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
};
const iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
};
const request = {
config: config,
@@ -58,90 +73,131 @@ describe('InstallationsRouter', () => {
body: {},
query: {
where: {
deviceType: 'android'
}
deviceType: 'android',
},
},
info: {}
info: {},
};
const router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
rest
.create(
config,
auth.nobody(config),
'_Installation',
androidDeviceRequest
)
.then(() => {
return rest.create(
config,
auth.nobody(config),
'_Installation',
iosDeviceRequest
);
})
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
})
.then(res => {
const results = res.response.results;
expect(results.length).toEqual(1);
done();
}).catch((err) => {
})
.catch(err => {
jfail(err);
done();
});
});
it('query installations with limit = 0', (done) => {
it('query installations with limit = 0', done => {
const config = Config.get('test');
const androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
};
const iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0
limit: 0,
},
info: {}
info: {},
};
Config.get('test');
const router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
rest
.create(
config,
auth.nobody(config),
'_Installation',
androidDeviceRequest
)
.then(() => {
return rest.create(
config,
auth.nobody(config),
'_Installation',
iosDeviceRequest
);
})
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
})
.then(res => {
const response = res.response;
expect(response.results.length).toEqual(0);
done();
}).catch((err) => {
})
.catch(err => {
fail(JSON.stringify(err));
done();
});
});
it('query installations with count = 1', done => {
it_exclude_dbs(['postgres'])('query installations with count = 1', done => {
const config = Config.get('test');
const androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
};
const iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
count: 1
count: 1,
},
info: {}
info: {},
};
const router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
rest
.create(
config,
auth.nobody(config),
'_Installation',
androidDeviceRequest
)
.then(() =>
rest.create(
config,
auth.nobody(config),
'_Installation',
iosDeviceRequest
)
)
.then(() => router.handleFind(request))
.then((res) => {
.then(res => {
const response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(2);
@@ -150,44 +206,108 @@ describe('InstallationsRouter', () => {
.catch(error => {
fail(JSON.stringify(error));
done();
})
});
});
it('query installations with limit = 0 and count = 1', (done) => {
it_only_db('postgres')('query installations with count = 1', async () => {
const config = Config.get('test');
const androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
};
const iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0,
count: 1
count: 1,
},
info: {}
info: {},
};
const router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
const response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
await rest.create(
config,
auth.nobody(config),
'_Installation',
androidDeviceRequest
);
await rest.create(
config,
auth.nobody(config),
'_Installation',
iosDeviceRequest
);
let res = await router.handleFind(request);
let response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(0); // estimate count is zero
const pgAdapter = config.database.adapter;
await pgAdapter.updateEstimatedCount('_Installation');
res = await router.handleFind(request);
response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(2);
});
it_exclude_dbs(['postgres'])(
'query installations with limit = 0 and count = 1',
done => {
const config = Config.get('test');
const androidDeviceRequest = {
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
};
const iosDeviceRequest = {
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0,
count: 1,
},
info: {},
};
const router = new InstallationsRouter();
rest
.create(
config,
auth.nobody(config),
'_Installation',
androidDeviceRequest
)
.then(() => {
return rest.create(
config,
auth.nobody(config),
'_Installation',
iosDeviceRequest
);
})
.then(() => {
return router.handleFind(request);
})
.then(res => {
const response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
})
.catch(err => {
fail(JSON.stringify(err));
done();
});
}
);
});
+219 -131
View File
@@ -1,198 +1,286 @@
const rp = require('request-promise');
const request = require('../lib/request');
const defaultHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest'
}
'X-Parse-Rest-API-Key': 'rest',
'Content-Type': 'application/json',
};
const masterKeyHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest',
'X-Parse-Master-Key': 'test'
}
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/json',
};
const defaultOptions = {
headers: defaultHeaders,
json: true
}
json: true,
};
const masterKeyOptions = {
headers: masterKeyHeaders,
json: true
}
json: true,
};
describe('JobSchedule', () => {
it('should create _JobSchedule with masterKey', (done) => {
it('should create _JobSchedule with masterKey', done => {
const jobSchedule = new Parse.Object('_JobSchedule');
jobSchedule.set({
'jobName': 'MY Cool Job'
jobName: 'MY Cool Job',
});
jobSchedule.save(null, {useMasterKey: true}).then(() => {
done();
})
.catch(done.fail);
});
it('should fail creating _JobSchedule without masterKey', (done) => {
const jobSchedule = new Parse.Object('_JobSchedule');
jobSchedule.set({
'jobName': 'SomeJob'
});
jobSchedule.save(null).then(done.fail)
.catch(() => done());
});
it('should reject access when not using masterKey (/jobs)', (done) => {
rp.get(Parse.serverURL + '/cloud_code/jobs', defaultOptions).then(done.fail, () => done());
});
it('should reject access when not using masterKey (/jobs/data)', (done) => {
rp.get(Parse.serverURL + '/cloud_code/jobs/data', defaultOptions).then(done.fail, () => done());
});
it('should reject access when not using masterKey (PUT /jobs/id)', (done) => {
rp.put(Parse.serverURL + '/cloud_code/jobs/jobId', defaultOptions).then(done.fail, () => done());
});
it('should reject access when not using masterKey (PUT /jobs/id)', (done) => {
rp.del(Parse.serverURL + '/cloud_code/jobs/jobId', defaultOptions).then(done.fail, () => done());
});
it('should allow access when using masterKey (/jobs)', (done) => {
rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions).then(done, done.fail);
});
it('should create a job schedule', (done) => {
Parse.Cloud.job('job', () => {});
const options = Object.assign({}, masterKeyOptions, {
body: {
job_schedule: {
jobName: 'job'
}
}
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
expect(res.objectId).not.toBeUndefined();
})
jobSchedule
.save(null, { useMasterKey: true })
.then(() => {
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
done();
})
.then((res) => {
expect(res.length).toBe(1);
})
.then(done)
.catch(done.fail);
});
it('should fail creating a job with an invalid name', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
job_schedule: {
jobName: 'job'
}
}
it('should fail creating _JobSchedule without masterKey', done => {
const jobSchedule = new Parse.Object('_JobSchedule');
jobSchedule.set({
jobName: 'SomeJob',
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options)
jobSchedule
.save(null)
.then(done.fail)
.catch(() => done());
});
it('should update a job', (done) => {
Parse.Cloud.job('job1', () => {});
Parse.Cloud.job('job2', () => {});
it('should reject access when not using masterKey (/jobs)', done => {
request(
Object.assign(
{ url: Parse.serverURL + '/cloud_code/jobs' },
defaultOptions
)
).then(done.fail, () => done());
});
it('should reject access when not using masterKey (/jobs/data)', done => {
request(
Object.assign(
{ url: Parse.serverURL + '/cloud_code/jobs/data' },
defaultOptions
)
).then(done.fail, () => done());
});
it('should reject access when not using masterKey (PUT /jobs/id)', done => {
request(
Object.assign(
{ method: 'PUT', url: Parse.serverURL + '/cloud_code/jobs/jobId' },
defaultOptions
)
).then(done.fail, () => done());
});
it('should reject access when not using masterKey (DELETE /jobs/id)', done => {
request(
Object.assign(
{ method: 'DELETE', url: Parse.serverURL + '/cloud_code/jobs/jobId' },
defaultOptions
)
).then(done.fail, () => done());
});
it('should allow access when using masterKey (GET /jobs)', done => {
request(
Object.assign(
{ url: Parse.serverURL + '/cloud_code/jobs' },
masterKeyOptions
)
).then(done, done.fail);
});
it('should create a job schedule', done => {
Parse.Cloud.job('job', () => {});
const options = Object.assign({}, masterKeyOptions, {
method: 'POST',
url: Parse.serverURL + '/cloud_code/jobs',
body: {
job_schedule: {
jobName: 'job1'
}
}
jobName: 'job',
},
},
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
expect(res.objectId).not.toBeUndefined();
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
body: {
job_schedule: {
jobName: 'job2'
}
}
}));
})
.then(() => {
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
request(options)
.then(res => {
expect(res.data.objectId).not.toBeUndefined();
})
.then((res) => {
expect(res.length).toBe(1);
expect(res[0].jobName).toBe('job2');
.then(() => {
return request(
Object.assign(
{ url: Parse.serverURL + '/cloud_code/jobs' },
masterKeyOptions
)
);
})
.then(res => {
expect(res.data.length).toBe(1);
})
.then(done)
.catch(done.fail);
});
it('should fail updating a job with an invalid name', (done) => {
Parse.Cloud.job('job1', () => {});
it('should fail creating a job with an invalid name', done => {
const options = Object.assign({}, masterKeyOptions, {
url: Parse.serverURL + '/cloud_code/jobs',
method: 'POST',
body: {
job_schedule: {
jobName: 'job1'
}
}
jobName: 'job',
},
},
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
expect(res.objectId).not.toBeUndefined();
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
body: {
job_schedule: {
jobName: 'job2'
}
}
}));
})
request(options)
.then(done.fail)
.catch(() => done());
});
it('should destroy a job', (done) => {
Parse.Cloud.job('job', () => {});
it('should update a job', done => {
Parse.Cloud.job('job1', () => {});
Parse.Cloud.job('job2', () => {});
const options = Object.assign({}, masterKeyOptions, {
method: 'POST',
url: Parse.serverURL + '/cloud_code/jobs',
body: {
job_schedule: {
jobName: 'job'
}
}
jobName: 'job1',
},
},
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
expect(res.objectId).not.toBeUndefined();
return rp.del(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, masterKeyOptions);
})
.then(() => {
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
request(options)
.then(res => {
expect(res.data.objectId).not.toBeUndefined();
return request(
Object.assign(options, {
url: Parse.serverURL + '/cloud_code/jobs/' + res.data.objectId,
method: 'PUT',
body: {
job_schedule: {
jobName: 'job2',
},
},
})
);
})
.then((res) => {
expect(res.length).toBe(0);
.then(() => {
return request(
Object.assign({}, masterKeyOptions, {
url: Parse.serverURL + '/cloud_code/jobs',
})
);
})
.then(res => {
expect(res.data.length).toBe(1);
expect(res.data[0].jobName).toBe('job2');
})
.then(done)
.catch(done.fail);
});
it('should properly return job data', (done) => {
it('should fail updating a job with an invalid name', done => {
Parse.Cloud.job('job1', () => {});
const options = Object.assign({}, masterKeyOptions, {
method: 'POST',
url: Parse.serverURL + '/cloud_code/jobs',
body: {
job_schedule: {
jobName: 'job1',
},
},
});
request(options)
.then(res => {
expect(res.data.objectId).not.toBeUndefined();
return request(
Object.assign(options, {
method: 'PUT',
url: Parse.serverURL + '/cloud_code/jobs/' + res.data.objectId,
body: {
job_schedule: {
jobName: 'job2',
},
},
})
);
})
.then(done.fail)
.catch(() => done());
});
it('should destroy a job', done => {
Parse.Cloud.job('job', () => {});
const options = Object.assign({}, masterKeyOptions, {
method: 'POST',
url: Parse.serverURL + '/cloud_code/jobs',
body: {
job_schedule: {
jobName: 'job',
},
},
});
request(options)
.then(res => {
expect(res.data.objectId).not.toBeUndefined();
return request(
Object.assign(
{
method: 'DELETE',
url: Parse.serverURL + '/cloud_code/jobs/' + res.data.objectId,
},
masterKeyOptions
)
);
})
.then(() => {
return request(
Object.assign(
{
url: Parse.serverURL + '/cloud_code/jobs',
},
masterKeyOptions
)
);
})
.then(res => {
expect(res.data.length).toBe(0);
})
.then(done)
.catch(done.fail);
});
it('should properly return job data', done => {
Parse.Cloud.job('job1', () => {});
Parse.Cloud.job('job2', () => {});
const options = Object.assign({}, masterKeyOptions, {
method: 'POST',
url: Parse.serverURL + '/cloud_code/jobs',
body: {
job_schedule: {
jobName: 'job1'
}
}
jobName: 'job1',
},
},
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
expect(res.objectId).not.toBeUndefined();
})
.then(() => {
return rp.get(Parse.serverURL + '/cloud_code/jobs/data', masterKeyOptions);
request(options)
.then(response => {
const res = response.data;
expect(res.objectId).not.toBeUndefined();
})
.then((res) => {
.then(() => {
return request(
Object.assign(
{ url: Parse.serverURL + '/cloud_code/jobs/data' },
masterKeyOptions
)
);
})
.then(response => {
const res = response.data;
expect(res.in_use).toEqual(['job1']);
expect(res.jobs).toContain('job1');
expect(res.jobs).toContain('job2');
expect(res.jobs.length).toBe(2);
})
.then(done)
.catch(done.fail);
.catch(e => done.fail(e.data));
});
});
+62 -30
View File
@@ -1,63 +1,95 @@
const logging = require('../src/Adapters/Logger/WinstonLogger');
const winston = require('winston');
const logging = require('../lib/Adapters/Logger/WinstonLogger');
const Transport = require('winston-transport');
class TestTransport extends winston.Transport {
log(level, msg, meta, callback) {
class TestTransport extends Transport {
log(info, callback) {
callback(null, true);
}
}
describe('Logger', () => {
describe('WinstonLogger', () => {
it('should add transport', () => {
const testTransport = new (TestTransport)({
name: 'test'
});
const testTransport = new TestTransport();
spyOn(testTransport, 'log');
logging.addTransport(testTransport);
expect(Object.keys(logging.logger.transports).length).toBe(4);
expect(logging.logger.transports.length).toBe(4);
logging.logger.info('hi');
expect(testTransport.log).toHaveBeenCalled();
logging.logger.error('error');
expect(testTransport.log).toHaveBeenCalled();
logging.removeTransport(testTransport);
expect(Object.keys(logging.logger.transports).length).toBe(3);
expect(logging.logger.transports.length).toBe(3);
});
it('should have files transports', (done) => {
it('should have files transports', done => {
reconfigureServer().then(() => {
const transports = logging.logger.transports;
const transportKeys = Object.keys(transports);
expect(transportKeys.length).toBe(3);
expect(transports.length).toBe(3);
done();
});
});
it('should disable files logs', (done) => {
it('should disable files logs', done => {
reconfigureServer({
logsFolder: null
logsFolder: null,
}).then(() => {
const transports = logging.logger.transports;
const transportKeys = Object.keys(transports);
expect(transportKeys.length).toBe(1);
expect(transports.length).toBe(1);
done();
});
});
it('should enable JSON logs', (done) => {
it('should have a timestamp', done => {
logging.logger.info('hi');
logging.logger.query({ limit: 1 }, (err, results) => {
if (err) {
done.fail(err);
}
expect(results['parse-server'][0].timestamp).toBeDefined();
done();
});
});
it('console should not be json', done => {
// Force console transport
reconfigureServer({
logsFolder: null,
silent: false,
})
.then(() => {
spyOn(process.stdout, 'write');
logging.logger.info('hi', { key: 'value' });
expect(process.stdout.write).toHaveBeenCalled();
const firstLog = process.stdout.write.calls.first().args[0];
expect(firstLog).toEqual('info: hi {"key":"value"}' + '\n');
return reconfigureServer();
})
.then(() => {
done();
});
});
it('should enable JSON logs', done => {
// Force console transport
reconfigureServer({
logsFolder: null,
jsonLogs: true,
silent: false
}).then(() => {
spyOn(process.stdout, 'write');
logging.logger.info('hi', {key: 'value'});
expect(process.stdout.write).toHaveBeenCalled();
const firstLog = process.stdout.write.calls.first().args[0];
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' }) + '\n');
return reconfigureServer({
jsonLogs: false
silent: false,
})
.then(() => {
spyOn(process.stdout, 'write');
logging.logger.info('hi', { key: 'value' });
expect(process.stdout.write).toHaveBeenCalled();
const firstLog = process.stdout.write.calls.first().args[0];
expect(firstLog).toEqual(
JSON.stringify({ key: 'value', level: 'info', message: 'hi' }) + '\n'
);
return reconfigureServer({
jsonLogs: false,
});
})
.then(() => {
done();
});
}).then(() => {
done();
});
});
});
+90 -38
View File
@@ -1,33 +1,40 @@
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const LoggerController = require('../lib/Controllers/LoggerController')
.LoggerController;
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
.WinstonLoggerAdapter;
describe('LoggerController', () => {
it('can check process a query without throwing', (done) => {
it('can process an empty query without throwing', done => {
// Make mock request
const query = {};
const loggerController = new LoggerController(new WinstonLoggerAdapter());
expect(() => {
loggerController.getLogs(query).then(function(res) {
expect(res.length).not.toBe(0);
done();
}).catch((err) => {
jfail(err);
done();
})
loggerController
.getLogs(query)
.then(function(res) {
expect(res.length).not.toBe(0);
done();
})
.catch(err => {
jfail(err);
done();
});
}).not.toThrow();
});
it('properly validates dateTimes', (done) => {
it('properly validates dateTimes', done => {
expect(LoggerController.validDateTime()).toBe(null);
expect(LoggerController.validDateTime("String")).toBe(null);
expect(LoggerController.validDateTime('String')).toBe(null);
expect(LoggerController.validDateTime(123456).getTime()).toBe(123456);
expect(LoggerController.validDateTime("2016-01-01Z00:00:00").getTime()).toBe(1451606400000);
expect(
LoggerController.validDateTime('2016-01-01Z00:00:00').getTime()
).toBe(1451606400000);
done();
});
it('can set the proper default values', (done) => {
it('can set the proper default values', done => {
// Make mock request
const result = LoggerController.parseOptions();
expect(result.size).toEqual(10);
@@ -37,14 +44,14 @@ describe('LoggerController', () => {
done();
});
it('can process a query without throwing', (done) => {
it('can parse an ascending query without throwing', done => {
// Make mock request
const query = {
from: "2016-01-01Z00:00:00",
until: "2016-01-01Z00:00:00",
from: '2016-01-01Z00:00:00',
until: '2016-01-01Z00:00:00',
size: 5,
order: 'asc',
level: 'error'
level: 'error',
};
const result = LoggerController.parseOptions(query);
@@ -58,50 +65,95 @@ describe('LoggerController', () => {
done();
});
it('can check process a query without throwing', (done) => {
// Make mock request
it('can process an ascending query without throwing', done => {
const query = {
from: "2016-01-01",
until: "2016-01-30",
size: 5,
order: 'desc',
level: 'error'
order: 'asc',
level: 'error',
};
const loggerController = new LoggerController(new WinstonLoggerAdapter());
expect(() => {
loggerController.getLogs(query).then(function(res) {
expect(res.length).toBe(0);
done();
}).catch((err) => {
jfail(err);
fail("should not fail");
done();
})
loggerController
.getLogs(query)
.then(function(res) {
expect(res.length).not.toBe(0);
done();
})
.catch(err => {
jfail(err);
fail('should not fail');
done();
});
}).not.toThrow();
});
it('should throw without an adapter', (done) => {
it('can parse a descending query without throwing', done => {
// Make mock request
const query = {
from: '2016-01-01Z00:00:00',
until: '2016-01-01Z00:00:00',
size: 5,
order: 'desc',
level: 'error',
};
const result = LoggerController.parseOptions(query);
expect(result.from.getTime()).toEqual(1451606400000);
expect(result.until.getTime()).toEqual(1451606400000);
expect(result.size).toEqual(5);
expect(result.order).toEqual('desc');
expect(result.level).toEqual('error');
done();
});
it('can process a descending query without throwing', done => {
const query = {
size: 5,
order: 'desc',
level: 'error',
};
const loggerController = new LoggerController(new WinstonLoggerAdapter());
expect(() => {
loggerController
.getLogs(query)
.then(function(res) {
expect(res.length).not.toBe(0);
done();
})
.catch(err => {
jfail(err);
fail('should not fail');
done();
});
}).not.toThrow();
});
it('should throw without an adapter', done => {
expect(() => {
new LoggerController();
}).toThrow();
done();
});
it('should replace implementations with verbose', (done) => {
it('should replace implementations with verbose', done => {
const adapter = new WinstonLoggerAdapter();
const logger = new LoggerController(adapter, null, {verbose: true });
spyOn(adapter, "log");
const logger = new LoggerController(adapter, null, { verbose: true });
spyOn(adapter, 'log');
logger.silly('yo!');
expect(adapter.log).not.toHaveBeenCalled();
done();
});
it('should replace implementations with logLevel', (done) => {
it('should replace implementations with logLevel', done => {
const adapter = new WinstonLoggerAdapter();
const logger = new LoggerController(adapter, null, { logLevel: 'error' });
spyOn(adapter, "log");
spyOn(adapter, 'log');
logger.warn('yo!');
logger.info('yo!');
logger.debug('yo!');
+94 -71
View File
@@ -1,23 +1,25 @@
'use strict';
const request = require('request');
const LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const request = require('../lib/request');
const LogsRouter = require('../lib/Routers/LogsRouter').LogsRouter;
const LoggerController = require('../lib/Controllers/LoggerController')
.LoggerController;
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
.WinstonLoggerAdapter;
const loggerController = new LoggerController(new WinstonLoggerAdapter());
describe('LogsRouter', () => {
it('can check valid master key of request', (done) => {
it('can check valid master key of request', done => {
// Make mock request
const request = {
auth: {
isMaster: true
isMaster: true,
},
query: {},
config: {
loggerController: loggerController
}
loggerController: loggerController,
},
};
const router = new LogsRouter();
@@ -28,16 +30,16 @@ describe('LogsRouter', () => {
done();
});
it('can check invalid construction of controller', (done) => {
it('can check invalid construction of controller', done => {
// Make mock request
const request = {
auth: {
isMaster: true
isMaster: true,
},
query: {},
config: {
loggerController: undefined // missing controller
}
loggerController: undefined, // missing controller
},
};
const router = new LogsRouter();
@@ -49,15 +51,15 @@ describe('LogsRouter', () => {
});
it('can check invalid master key of request', done => {
request.get({
request({
url: 'http://localhost:8378/1/scriptlog',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(403);
'X-Parse-REST-API-Key': 'rest',
},
}).then(fail, response => {
const body = response.data;
expect(response.status).toEqual(403);
expect(body.error).toEqual('unauthorized: master key is required');
done();
});
@@ -66,7 +68,7 @@ describe('LogsRouter', () => {
const headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Master-Key': 'test'
'X-Parse-Master-Key': 'test',
};
/**
@@ -74,24 +76,31 @@ describe('LogsRouter', () => {
*/
it('does scrub simple passwords on GET login', done => {
reconfigureServer({
verbose: true
verbose: true,
}).then(function() {
request.get({
request({
headers: headers,
url: 'http://localhost:8378/1/login?username=test&password=simplepass.com'
}, () => {
request.get({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
json: true,
headers: headers
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
// 4th entry is our actual GET request
expect(body[3].url).toEqual('/1/login?username=test&password=********');
expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}');
done();
url:
'http://localhost:8378/1/login?username=test&password=simplepass.com',
})
.catch(() => {})
.then(() => {
request({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
headers: headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
// 4th entry is our actual GET request
expect(body[2].url).toEqual(
'/1/login?username=test&password=********'
);
expect(body[2].message).toEqual(
'REQUEST for [GET] /1/login?username=test&password=********: {}'
);
done();
});
});
});
});
});
@@ -100,26 +109,35 @@ describe('LogsRouter', () => {
*/
it('does scrub complex passwords on GET login', done => {
reconfigureServer({
verbose: true
}).then(function() {
request.get({
headers: headers,
// using urlencoded password, 'simple @,/?:&=+$#pass.com'
url: 'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com'
}, () => {
request.get({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
json: true,
headers: headers
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
// 4th entry is our actual GET request
expect(body[3].url).toEqual('/1/login?username=test&password=********');
expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}');
done();
});
});
});
verbose: true,
})
.then(function() {
return request({
headers: headers,
// using urlencoded password, 'simple @,/?:&=+$#pass.com'
url:
'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com',
})
.catch(() => {})
.then(() => {
return request({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
headers: headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
// 4th entry is our actual GET request
expect(body[2].url).toEqual(
'/1/login?username=test&password=********'
);
expect(body[2].message).toEqual(
'REQUEST for [GET] /1/login?username=test&password=********: {}'
);
done();
});
});
})
.catch(done.fail);
});
/**
@@ -127,28 +145,33 @@ describe('LogsRouter', () => {
*/
it('does not have password field in POST login', done => {
reconfigureServer({
verbose: true
verbose: true,
}).then(function() {
request.post({
request({
method: 'POST',
headers: headers,
url: 'http://localhost:8378/1/login',
data: {
body: {
username: 'test',
password: 'simplepass.com'
}
}, () => {
request.get({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
json: true,
headers: headers
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
// 4th entry is our actual GET request
expect(body[3].url).toEqual('/1/login');
expect(body[3].message).toEqual('REQUEST for [POST] /1/login: {}');
done();
password: 'simplepass.com',
},
})
.catch(() => {})
.then(() => {
request({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
headers: headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
// 4th entry is our actual GET request
expect(body[2].url).toEqual('/1/login');
expect(body[2].message).toEqual(
'REQUEST for [POST] /1/login: {\n "username": "test",\n "password": "********"\n}'
);
done();
});
});
});
});
});
});
+71 -57
View File
@@ -1,8 +1,7 @@
const middlewares = require('../src/middlewares');
const AppCache = require('../src/cache').AppCache;
const middlewares = require('../lib/middlewares');
const AppCache = require('../lib/cache').AppCache;
describe('middlewares', () => {
let fakeReq, fakeRes;
beforeEach(() => {
@@ -10,12 +9,12 @@ describe('middlewares', () => {
originalUrl: 'http://example.com/parse/',
url: 'http://example.com/',
body: {
_ApplicationId: 'FakeAppId'
_ApplicationId: 'FakeAppId',
},
headers: {},
get: (key) => {
return fakeReq.headers[key.toLowerCase()]
}
get: key => {
return fakeReq.headers[key.toLowerCase()];
},
};
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
AppCache.put(fakeReq.body._ApplicationId, {});
@@ -25,21 +24,21 @@ describe('middlewares', () => {
AppCache.del(fakeReq.body._ApplicationId);
});
it('should use _ContentType if provided', (done) => {
it('should use _ContentType if provided', done => {
expect(fakeReq.headers['content-type']).toEqual(undefined);
const contentType = 'image/jpeg';
fakeReq.body._ContentType = contentType;
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeReq.headers['content-type']).toEqual(contentType);
expect(fakeReq.body._ContentType).toEqual(undefined);
done()
done();
});
});
it('should give invalid response when keys are configured but no key supplied', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
restAPIKey: 'restAPIKey',
});
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
@@ -48,7 +47,7 @@ describe('middlewares', () => {
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
restAPIKey: 'restAPIKey',
});
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
@@ -58,19 +57,18 @@ describe('middlewares', () => {
it('should give invalid response when keys are configured but different key is supplied', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
restAPIKey: 'restAPIKey',
});
fakeReq.headers['x-parse-client-key'] = 'clientKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed when any one of the configured keys supplied', (done) => {
it('should succeed when any one of the configured keys supplied', done => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: 'clientKey',
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
restAPIKey: 'restAPIKey',
});
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
@@ -79,11 +77,11 @@ describe('middlewares', () => {
});
});
it('should succeed when client key supplied but empty', (done) => {
it('should succeed when client key supplied but empty', done => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: '',
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
restAPIKey: 'restAPIKey',
});
fakeReq.headers['x-parse-client-key'] = '';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
@@ -92,9 +90,9 @@ describe('middlewares', () => {
});
});
it('should succeed when no keys are configured and none supplied', (done) => {
it('should succeed when no keys are configured and none supplied', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey'
masterKey: 'masterKey',
});
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
@@ -107,25 +105,27 @@ describe('middlewares', () => {
installationId: '_InstallationId',
sessionToken: '_SessionToken',
masterKey: '_MasterKey',
javascriptKey: '_JavaScriptKey'
javascriptKey: '_JavaScriptKey',
};
const BodyKeys = Object.keys(BodyParams);
BodyKeys.forEach((infoKey) => {
BodyKeys.forEach(infoKey => {
const bodyKey = BodyParams[infoKey];
const keyValue = 'Fake' + bodyKey;
// javascriptKey is the only one that gets defaulted,
const otherKeys = BodyKeys.filter((otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey');
const otherKeys = BodyKeys.filter(
otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey'
);
it(`it should pull ${bodyKey} into req.info`, (done) => {
it(`it should pull ${bodyKey} into req.info`, done => {
fakeReq.body[bodyKey] = keyValue;
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeReq.body[bodyKey]).toEqual(undefined);
expect(fakeReq.info[infoKey]).toEqual(keyValue);
otherKeys.forEach((otherKey) => {
otherKeys.forEach(otherKey => {
expect(fakeReq.info[otherKey]).toEqual(undefined);
});
@@ -137,7 +137,7 @@ describe('middlewares', () => {
it('should not succeed if the ip does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.ip = 'ip3';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
@@ -145,14 +145,14 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the ip does belong to masterKeyIps list', (done) => {
it('should succeed if the ip does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.ip = 'ip1';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
@@ -161,22 +161,22 @@ describe('middlewares', () => {
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = {remoteAddress : 'ip3'};
fakeReq.connection = { remoteAddress: 'ip3' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', (done) => {
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = {remoteAddress : 'ip1'};
fakeReq.connection = { remoteAddress: 'ip1' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
@@ -185,22 +185,22 @@ describe('middlewares', () => {
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.socket = {remoteAddress : 'ip3'};
fakeReq.socket = { remoteAddress: 'ip3' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', (done) => {
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.socket = {remoteAddress : 'ip1'};
fakeReq.socket = { remoteAddress: 'ip1' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
@@ -209,61 +209,61 @@ describe('middlewares', () => {
it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = { socket : {remoteAddress : 'ip3'}};
fakeReq.connection = { socket: { remoteAddress: 'ip3' } };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', (done) => {
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = { socket : {remoteAddress : 'ip1'}};
fakeReq.connection = { socket: { remoteAddress: 'ip1' } };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should allow any ip to use masterKey if masterKeyIps is empty', (done) => {
it('should allow any ip to use masterKey if masterKeyIps is empty', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: []
masterKeyIps: [],
});
fakeReq.ip = 'ip1';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed if xff header does belong to masterKeyIps', (done) => {
it('should succeed if xff header does belong to masterKeyIps', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1']
masterKeyIps: ['ip1'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed if xff header with one ip does belong to masterKeyIps', (done) => {
it('should succeed if xff header with one ip does belong to masterKeyIps', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1']
masterKeyIps: ['ip1'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
@@ -272,7 +272,7 @@ describe('middlewares', () => {
it('should not succeed if xff header does not belong to masterKeyIps', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip4']
masterKeyIps: ['ip4'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
@@ -283,11 +283,25 @@ describe('middlewares', () => {
it('should not succeed if xff header is empty and masterKeyIps is set', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1']
masterKeyIps: ['ip1'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = '';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should properly expose the headers', () => {
const headers = {};
const res = {
header: (key, value) => {
headers[key] = value;
},
};
middlewares.allowCrossDomain({}, res, () => {});
expect(Object.keys(headers).length).toBe(4);
expect(headers['Access-Control-Expose-Headers']).toBe(
'X-Parse-Job-Status-Id, X-Parse-Push-Status-Id'
);
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = function(options) {
return {
options: options
options: options,
};
};
+2 -2
View File
@@ -1,5 +1,5 @@
module.exports = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => Promise.resolve()
}
sendMail: () => Promise.resolve(),
};
+5 -5
View File
@@ -1,21 +1,21 @@
module.exports = options => {
if (!options) {
throw "Options were not provided"
throw 'Options were not provided';
}
const adapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => Promise.resolve()
sendMail: () => Promise.resolve(),
};
if (options.sendMail) {
adapter.sendMail = options.sendMail
adapter.sendMail = options.sendMail;
}
if (options.sendPasswordResetEmail) {
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail;
}
if (options.sendVerificationEmail) {
adapter.sendVerificationEmail = options.sendVerificationEmail;
}
return adapter;
}
};
+1 -1
View File
@@ -4,6 +4,6 @@ module.exports = function(options) {
send: function() {},
getValidPushTypes: function() {
return Object.keys(options.options);
}
},
};
};
+46 -38
View File
@@ -1,46 +1,52 @@
'use strict';
const MongoSchemaCollection = require('../src/Adapters/Storage/Mongo/MongoSchemaCollection').default;
const MongoSchemaCollection = require('../lib/Adapters/Storage/Mongo/MongoSchemaCollection')
.default;
describe('MongoSchemaCollection', () => {
it('can transform legacy _client_permissions keys to parse format', done => {
expect(MongoSchemaCollection._TESTmongoSchemaToParseSchema({
"_id":"_Installation",
"_client_permissions":{
"get":true,
"find":true,
"update":true,
"create":true,
"delete":true,
},
"_metadata":{
"class_permissions":{
"get":{"*":true},
"find":{"*":true},
"update":{"*":true},
"create":{"*":true},
"delete":{"*":true},
"addField":{"*":true},
expect(
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: '_Installation',
_client_permissions: {
get: true,
find: true,
count: true,
update: true,
create: true,
delete: true,
},
"indexes": {
"name1":{"deviceToken":1}
}
},
"installationId":"string",
"deviceToken":"string",
"deviceType":"string",
"channels":"array",
"user":"*_User",
"pushType":"string",
"GCMSenderId":"string",
"timeZone":"string",
"localeIdentifier":"string",
"badge":"number",
"appVersion":"string",
"appName":"string",
"appIdentifier":"string",
"parseVersion":"string",
})).toEqual({
_metadata: {
class_permissions: {
get: { '*': true },
find: { '*': true },
count: { '*': true },
update: { '*': true },
create: { '*': true },
delete: { '*': true },
addField: { '*': true },
protectedFields: { '*': [] },
},
indexes: {
name1: { deviceToken: 1 },
},
},
installationId: 'string',
deviceToken: 'string',
deviceType: 'string',
channels: 'array',
user: '*_User',
pushType: 'string',
GCMSenderId: 'string',
timeZone: 'string',
localeIdentifier: 'string',
badge: 'number',
appVersion: 'string',
appName: 'string',
appIdentifier: 'string',
parseVersion: 'string',
})
).toEqual({
className: '_Installation',
fields: {
installationId: { type: 'String' },
@@ -65,13 +71,15 @@ describe('MongoSchemaCollection', () => {
classLevelPermissions: {
find: { '*': true },
get: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
protectedFields: { '*': [] },
},
indexes: {
name1: {deviceToken: 1}
name1: { deviceToken: 1 },
},
});
done();
+103 -50
View File
@@ -1,8 +1,15 @@
'use strict';
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
.default;
const { MongoClient } = require('mongodb');
const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const databaseURI =
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const fakeClient = {
s: { options: { dbName: null } },
db: () => null,
};
// These tests are specific to the mongo storage adapter + mongo storage format
// and will eventually be moved into their own repo
@@ -14,9 +21,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
it('auto-escapes symbols in auth information', () => {
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(fakeClient));
new MongoStorageAdapter({
uri: 'mongodb://user!with@+ symbols:password!with@+ symbols@localhost:1234/parse'
uri:
'mongodb://user!with@+ symbols:password!with@+ symbols@localhost:1234/parse',
}).connect();
expect(MongoClient.connect).toHaveBeenCalledWith(
'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse',
@@ -25,9 +33,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
it("doesn't double escape already URI-encoded information", () => {
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(fakeClient));
new MongoStorageAdapter({
uri: 'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse'
uri:
'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse',
}).connect();
expect(MongoClient.connect).toHaveBeenCalledWith(
'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse',
@@ -37,9 +46,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
// https://github.com/parse-community/parse-server/pull/148#issuecomment-180407057
it('preserves replica sets', () => {
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(fakeClient));
new MongoStorageAdapter({
uri: 'mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415'
uri:
'mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415',
}).connect();
expect(MongoClient.connect).toHaveBeenCalledWith(
'mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415',
@@ -49,7 +59,8 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
it('stores objectId in _id', done => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
adapter
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', {}))
.then(results => {
expect(results.length).toEqual(1);
@@ -60,57 +71,70 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
});
it('find succeeds when query is within maxTimeMS', (done) => {
it('find succeeds when query is within maxTimeMS', done => {
const maxTimeMS = 250;
const adapter = new MongoStorageAdapter({
uri: databaseURI,
mongoOptions: { maxTimeMS },
});
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS / 2})` }))
adapter
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() =>
adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS / 2})` })
)
.then(
() => done(),
(err) => {
err => {
done.fail(`maxTimeMS should not affect fast queries ${err}`);
}
);
})
});
it('find fails when query exceeds maxTimeMS', (done) => {
it('find fails when query exceeds maxTimeMS', done => {
const maxTimeMS = 250;
const adapter = new MongoStorageAdapter({
uri: databaseURI,
mongoOptions: { maxTimeMS },
});
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS * 2})` }))
adapter
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() =>
adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS * 2})` })
)
.then(
() => {
done.fail('Find succeeded despite taking too long!');
},
(err) => {
err => {
expect(err.name).toEqual('MongoError');
expect(err.code).toEqual(50);
expect(err.message).toEqual('operation exceeded time limit');
expect(err.message).toMatch('operation exceeded time limit');
done();
}
);
});
it('stores pointers with a _p_ prefix', (done) => {
it('stores pointers with a _p_ prefix', done => {
const obj = {
objectId: 'bar',
aPointer: {
__type: 'Pointer',
className: 'JustThePointer',
objectId: 'qwerty'
}
objectId: 'qwerty',
},
};
const adapter = new MongoStorageAdapter({ uri: databaseURI });
adapter.createObject('APointerDarkly', { fields: {
objectId: { type: 'String' },
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
}}, obj)
adapter
.createObject(
'APointerDarkly',
{
fields: {
objectId: { type: 'String' },
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
},
},
obj
)
.then(() => adapter._rawFind('APointerDarkly', {}))
.then(results => {
expect(results.length).toEqual(1);
@@ -125,9 +149,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
it('handles object and subdocument', done => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const schema = { fields : { subdoc: { type: 'Object' } } };
const obj = { subdoc: {foo: 'bar', wu: 'tan'} };
adapter.createObject('MyClass', schema, obj)
const schema = { fields: { subdoc: { type: 'Object' } } };
const obj = { subdoc: { foo: 'bar', wu: 'tan' } };
adapter
.createObject('MyClass', schema, obj)
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
@@ -149,22 +174,25 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
});
it('handles creating an array, object, date', (done) => {
it('handles creating an array, object, date', done => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const obj = {
array: [1, 2, 3],
object: {foo: 'bar'},
object: { foo: 'bar' },
date: {
__type: 'Date',
iso: '2016-05-26T20:55:01.154Z',
},
};
const schema = { fields: {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
} };
adapter.createObject('MyClass', schema, obj)
const schema = {
fields: {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
},
};
adapter
.createObject('MyClass', schema, obj)
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
@@ -190,30 +218,32 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
});
it("handles updating a single object with array, object date", (done) => {
it('handles updating a single object with array, object date', done => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const schema = { fields: {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
} };
const schema = {
fields: {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
},
};
adapter.createObject('MyClass', schema, {})
adapter
.createObject('MyClass', schema, {})
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
const update = {
array: [1, 2, 3],
object: {foo: 'bar'},
object: { foo: 'bar' },
date: {
__type: 'Date',
iso: '2016-05-26T20:55:01.154Z',
},
};
const query = {};
return adapter.findOneAndUpdate('MyClass', schema, query, update)
return adapter.findOneAndUpdate('MyClass', schema, query, update);
})
.then(results => {
const mob = results;
@@ -238,7 +268,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
});
it('handleShutdown, close connection', (done) => {
it('handleShutdown, close connection', done => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const schema = {
@@ -246,14 +276,37 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
}
},
};
adapter.createObject('MyClass', schema, {}).then(() => {
expect(adapter.database.serverConfig.isConnected()).toEqual(true);
adapter.handleShutdown()
adapter.handleShutdown();
expect(adapter.database.serverConfig.isConnected()).toEqual(false);
done();
});
});
it('getClass if exists', async () => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const schema = {
fields: {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
},
};
await adapter.createClass('MyClass', schema);
const myClassSchema = await adapter.getClass('MyClass');
expect(myClassSchema).toBeDefined();
});
it('getClass if not exists', async () => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(
undefined
);
});
});
+277 -128
View File
@@ -1,132 +1,184 @@
// These tests are unit tests designed to only test transform.js.
"use strict";
'use strict';
const transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
const transform = require('../lib/Adapters/Storage/Mongo/MongoTransform');
const dd = require('deep-diff');
const mongodb = require('mongodb');
describe('parseObjectToMongoObjectForCreate', () => {
it('a basic number', (done) => {
const input = {five: 5};
it('a basic number', done => {
const input = { five: 5 };
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {five: {type: 'Number'}}
fields: { five: { type: 'Number' } },
});
jequal(input, output);
done();
});
it('an object with null values', (done) => {
const input = {objectWithNullValues: {isNull: null, notNull: 3}};
it('an object with null values', done => {
const input = { objectWithNullValues: { isNull: null, notNull: 3 } };
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {objectWithNullValues: {type: 'object'}}
fields: { objectWithNullValues: { type: 'object' } },
});
jequal(input, output);
done();
});
it('built-in timestamps', (done) => {
it('built-in timestamps with date', done => {
const input = {
createdAt: "2015-10-06T21:24:50.332Z",
updatedAt: "2015-10-06T21:24:50.332Z"
createdAt: '2015-10-06T21:24:50.332Z',
updatedAt: '2015-10-06T21:24:50.332Z',
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {},
});
expect(output._created_at instanceof Date).toBe(true);
expect(output._updated_at instanceof Date).toBe(true);
done();
});
it('array of pointers', (done) => {
it('array of pointers', done => {
const pointer = {
__type: 'Pointer',
objectId: 'myId',
className: 'Blah',
};
const out = transform.parseObjectToMongoObjectForCreate(null, {pointers: [pointer]},{
fields: {pointers: {type: 'Array'}}
});
const out = transform.parseObjectToMongoObjectForCreate(
null,
{ pointers: [pointer] },
{
fields: { pointers: { type: 'Array' } },
}
);
jequal([pointer], out.pointers);
done();
});
//TODO: object creation requests shouldn't be seeing __op delete, it makes no sense to
//have __op delete in a new object. Figure out what this should actually be testing.
xit('a delete op', (done) => {
const input = {deleteMe: {__op: 'Delete'}};
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
xit('a delete op', done => {
const input = { deleteMe: { __op: 'Delete' } };
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {},
});
jequal(output, {});
done();
});
it('Doesnt allow ACL, as Parse Server should tranform ACL to _wperm + _rperm', done => {
const input = {ACL: {'0123': {'read': true, 'write': true}}};
expect(() => transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} })).toThrow();
const input = { ACL: { '0123': { read: true, write: true } } };
expect(() =>
transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} })
).toThrow();
done();
});
it('plain', (done) => {
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
const out = transform.parseObjectToMongoObjectForCreate(null, {location: geoPoint},{
fields: {location: {type: 'GeoPoint'}}
});
expect(out.location).toEqual([180, -180]);
it('parse geopoint to mongo', done => {
const lat = -45;
const lng = 45;
const geoPoint = { __type: 'GeoPoint', latitude: lat, longitude: lng };
const out = transform.parseObjectToMongoObjectForCreate(
null,
{ location: geoPoint },
{
fields: { location: { type: 'GeoPoint' } },
}
);
expect(out.location).toEqual([lng, lat]);
done();
});
it('in array', (done) => {
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
const out = transform.parseObjectToMongoObjectForCreate(null, {locations: [geoPoint, geoPoint]},{
fields: {locations: {type: 'Array'}}
});
it('parse polygon to mongo', done => {
const lat1 = -45;
const lng1 = 45;
const lat2 = -55;
const lng2 = 55;
const lat3 = -65;
const lng3 = 65;
const polygon = {
__type: 'Polygon',
coordinates: [[lat1, lng1], [lat2, lng2], [lat3, lng3]],
};
const out = transform.parseObjectToMongoObjectForCreate(
null,
{ location: polygon },
{
fields: { location: { type: 'Polygon' } },
}
);
expect(out.location.coordinates).toEqual([
[[lng1, lat1], [lng2, lat2], [lng3, lat3], [lng1, lat1]],
]);
done();
});
it('in array', done => {
const geoPoint = { __type: 'GeoPoint', longitude: 180, latitude: -180 };
const out = transform.parseObjectToMongoObjectForCreate(
null,
{ locations: [geoPoint, geoPoint] },
{
fields: { locations: { type: 'Array' } },
}
);
expect(out.locations).toEqual([geoPoint, geoPoint]);
done();
});
it('in sub-object', (done) => {
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
const out = transform.parseObjectToMongoObjectForCreate(null, { locations: { start: geoPoint }},{
fields: {locations: {type: 'Object'}}
});
it('in sub-object', done => {
const geoPoint = { __type: 'GeoPoint', longitude: 180, latitude: -180 };
const out = transform.parseObjectToMongoObjectForCreate(
null,
{ locations: { start: geoPoint } },
{
fields: { locations: { type: 'Object' } },
}
);
expect(out).toEqual({ locations: { start: geoPoint } });
done();
});
it('objectId', (done) => {
const out = transform.transformWhere(null, {objectId: 'foo'});
it('objectId', done => {
const out = transform.transformWhere(null, { objectId: 'foo' });
expect(out._id).toEqual('foo');
done();
});
it('objectId in a list', (done) => {
it('objectId in a list', done => {
const input = {
objectId: {'$in': ['one', 'two', 'three']},
objectId: { $in: ['one', 'two', 'three'] },
};
const output = transform.transformWhere(null, input);
jequal(input.objectId, output._id);
done();
});
it('built-in timestamps', (done) => {
const input = {createdAt: new Date(), updatedAt: new Date()};
const output = transform.mongoObjectToParseObject(null, input, { fields: {} });
it('built-in timestamps', done => {
const input = { createdAt: new Date(), updatedAt: new Date() };
const output = transform.mongoObjectToParseObject(null, input, {
fields: {},
});
expect(typeof output.createdAt).toEqual('string');
expect(typeof output.updatedAt).toEqual('string');
done();
});
it('pointer', (done) => {
const input = {_p_userPointer: '_User$123'};
it('pointer', done => {
const input = { _p_userPointer: '_User$123' };
const output = transform.mongoObjectToParseObject(null, input, {
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
});
expect(typeof output.userPointer).toEqual('object');
expect(output.userPointer).toEqual(
{__type: 'Pointer', className: '_User', objectId: '123'}
);
expect(output.userPointer).toEqual({
__type: 'Pointer',
className: '_User',
objectId: '123',
});
done();
});
it('null pointer', (done) => {
const input = {_p_userPointer: null};
it('null pointer', done => {
const input = { _p_userPointer: null };
const output = transform.mongoObjectToParseObject(null, input, {
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
});
@@ -134,112 +186,134 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('file', (done) => {
const input = {picture: 'pic.jpg'};
it('file', done => {
const input = { picture: 'pic.jpg' };
const output = transform.mongoObjectToParseObject(null, input, {
fields: { picture: { type: 'File' }},
fields: { picture: { type: 'File' } },
});
expect(typeof output.picture).toEqual('object');
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
expect(output.picture).toEqual({ __type: 'File', name: 'pic.jpg' });
done();
});
it('geopoint', (done) => {
const input = {location: [45, -45]};
it('mongo geopoint to parse', done => {
const lat = -45;
const lng = 45;
const input = { location: [lng, lat] };
const output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'GeoPoint' }},
fields: { location: { type: 'GeoPoint' } },
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', longitude: 45, latitude: -45}
);
expect(output.location).toEqual({
__type: 'GeoPoint',
latitude: lat,
longitude: lng,
});
done();
});
it('polygon', (done) => {
const input = {location: { type: 'Polygon', coordinates: [[[45, -45],[45, -45]]]}};
it('mongo polygon to parse', done => {
const lat = -45;
const lng = 45;
// Mongo stores polygon in WGS84 lng/lat
const input = {
location: { type: 'Polygon', coordinates: [[[lat, lng], [lat, lng]]] },
};
const output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'Polygon' }},
fields: { location: { type: 'Polygon' } },
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'Polygon', coordinates: [[45, -45],[45, -45]]}
);
expect(output.location).toEqual({
__type: 'Polygon',
coordinates: [[lng, lat], [lng, lat]],
});
done();
});
it('bytes', (done) => {
const input = {binaryData: "aGVsbG8gd29ybGQ="};
it('bytes', done => {
const input = { binaryData: 'aGVsbG8gd29ybGQ=' };
const output = transform.mongoObjectToParseObject(null, input, {
fields: { binaryData: { type: 'Bytes' }},
fields: { binaryData: { type: 'Bytes' } },
});
expect(typeof output.binaryData).toEqual('object');
expect(output.binaryData).toEqual(
{__type: 'Bytes', base64: "aGVsbG8gd29ybGQ="}
);
expect(output.binaryData).toEqual({
__type: 'Bytes',
base64: 'aGVsbG8gd29ybGQ=',
});
done();
});
it('nested array', (done) => {
const input = {arr: [{_testKey: 'testValue' }]};
it('nested array', done => {
const input = { arr: [{ _testKey: 'testValue' }] };
const output = transform.mongoObjectToParseObject(null, input, {
fields: { arr: { type: 'Array' } },
});
expect(Array.isArray(output.arr)).toEqual(true);
expect(output.arr).toEqual([{ _testKey: 'testValue'}]);
expect(output.arr).toEqual([{ _testKey: 'testValue' }]);
done();
});
it('untransforms objects containing nested special keys', done => {
const input = {array: [{
_id: "Test ID",
_hashed_password: "I Don't know why you would name a key this, but if you do it should work",
_tombstone: {
_updated_at: "I'm sure people will nest keys like this",
_acl: 7,
_id: { someString: "str", someNumber: 7},
regularKey: { moreContents: [1, 2, 3] },
},
regularKey: "some data",
}]}
const input = {
array: [
{
_id: 'Test ID',
_hashed_password:
"I Don't know why you would name a key this, but if you do it should work",
_tombstone: {
_updated_at: "I'm sure people will nest keys like this",
_acl: 7,
_id: { someString: 'str', someNumber: 7 },
regularKey: { moreContents: [1, 2, 3] },
},
regularKey: 'some data',
},
],
};
const output = transform.mongoObjectToParseObject(null, input, {
fields: { array: { type: 'Array' }},
fields: { array: { type: 'Array' } },
});
expect(dd(output, input)).toEqual(undefined);
done();
});
it('changes new pointer key', (done) => {
it('changes new pointer key', done => {
const input = {
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
somePointer: { __type: 'Pointer', className: 'Micro', objectId: 'oft' },
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {somePointer: {type: 'Pointer'}}
fields: { somePointer: { type: 'Pointer' } },
});
expect(typeof output._p_somePointer).toEqual('string');
expect(output._p_somePointer).toEqual('Micro$oft');
done();
});
it('changes existing pointer keys', (done) => {
it('changes existing pointer keys', done => {
const input = {
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
userPointer: {
__type: 'Pointer',
className: '_User',
objectId: 'qwerty',
},
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {userPointer: {type: 'Pointer'}}
fields: { userPointer: { type: 'Pointer' } },
});
expect(typeof output._p_userPointer).toEqual('string');
expect(output._p_userPointer).toEqual('_User$qwerty');
done();
});
it('writes the old ACL format in addition to rperm and wperm on create', (done) => {
it('writes the old ACL format in addition to rperm and wperm on create', done => {
const input = {
_rperm: ['*'],
_wperm: ['Kevin'],
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {},
});
expect(typeof output._acl).toEqual('object');
expect(output._acl['Kevin'].w).toBeTruthy();
expect(output._acl['Kevin'].r).toBeUndefined();
@@ -248,7 +322,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('removes Relation types', (done) => {
it('removes Relation types', done => {
const input = {
aRelation: { __type: 'Relation', className: 'Stuff' },
};
@@ -261,10 +335,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
it('writes the old ACL format in addition to rperm and wperm on update', done => {
const input = {
_rperm: ['*'],
_wperm: ['Kevin']
_wperm: ['Kevin'],
};
const output = transform.transformUpdate(null, input, { fields: {} });
@@ -278,23 +352,25 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('untransforms from _rperm and _wperm to ACL', (done) => {
it('untransforms from _rperm and _wperm to ACL', done => {
const input = {
_rperm: ["*"],
_wperm: ["Kevin"]
_rperm: ['*'],
_wperm: ['Kevin'],
};
const output = transform.mongoObjectToParseObject(null, input, { fields: {} });
const output = transform.mongoObjectToParseObject(null, input, {
fields: {},
});
expect(output._rperm).toEqual(['*']);
expect(output._wperm).toEqual(['Kevin']);
expect(output.ACL).toBeUndefined()
expect(output.ACL).toBeUndefined();
done();
});
it('untransforms mongodb number types', (done) => {
it('untransforms mongodb number types', done => {
const input = {
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
double: new mongodb.Double(Number.MAX_VALUE)
}
double: new mongodb.Double(Number.MAX_VALUE),
};
const output = transform.mongoObjectToParseObject(null, input, {
fields: {
long: { type: 'Number' },
@@ -306,35 +382,107 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('Date object where iso attribute is of type Date', (done) => {
it('Date object where iso attribute is of type Date', done => {
const input = {
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
}
ts: { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') },
};
const output = transform.mongoObjectToParseObject(null, input, {
fields : {
ts : { type : 'Date' }
}
fields: {
ts: { type: 'Date' },
},
});
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
done();
});
it('Date object where iso attribute is of type String', (done) => {
it('Date object where iso attribute is of type String', done => {
const input = {
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
}
ts: { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' },
};
const output = transform.mongoObjectToParseObject(null, input, {
fields : {
ts : { type : 'Date' }
}
fields: {
ts: { type: 'Date' },
},
});
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
done();
});
it('object with undefined nested values', () => {
const input = {
_id: 'vQHyinCW1l',
urls: { firstUrl: 'https://', secondUrl: undefined },
};
const output = transform.mongoObjectToParseObject(null, input, {
fields: {
urls: { type: 'Object' },
},
});
expect(output.urls).toEqual({
firstUrl: 'https://',
secondUrl: undefined,
});
});
it('undefined objects', () => {
const input = {
_id: 'vQHyinCW1l',
urls: undefined,
};
const output = transform.mongoObjectToParseObject(null, input, {
fields: {
urls: { type: 'Object' },
},
});
expect(output.urls).toBeUndefined();
});
it('$regex in $all list', done => {
const input = {
arrayField: {
$all: [
{ $regex: '^\\Qone\\E' },
{ $regex: '^\\Qtwo\\E' },
{ $regex: '^\\Qthree\\E' },
],
},
};
const outputValue = {
arrayField: { $all: [/^\Qone\E/, /^\Qtwo\E/, /^\Qthree\E/] },
};
const output = transform.transformWhere(null, input);
jequal(outputValue.arrayField, output.arrayField);
done();
});
it('$regex in $all list must be { $regex: "string" }', done => {
const input = {
arrayField: { $all: [{ $regex: 1 }] },
};
expect(() => {
transform.transformWhere(null, input);
}).toThrow();
done();
});
it('all values in $all must be $regex (start with string) or non $regex (start with string)', done => {
const input = {
arrayField: {
$all: [{ $regex: '^\\Qone\\E' }, { $unknown: '^\\Qtwo\\E' }],
},
};
expect(() => {
transform.transformWhere(null, input);
}).toThrow();
done();
});
});
describe('transformUpdate', () => {
it('removes Relation types', (done) => {
it('removes Relation types', done => {
const input = {
aRelation: { __type: 'Relation', className: 'Stuff' },
};
@@ -356,8 +504,8 @@ describe('transformConstraint', () => {
$eq: {
ttl: {
$relativeTime: '12 days ago',
}
}
},
},
});
}).toThrow();
@@ -366,8 +514,8 @@ describe('transformConstraint', () => {
$ne: {
ttl: {
$relativeTime: '12 days ago',
}
}
},
},
});
}).toThrow();
@@ -375,11 +523,11 @@ describe('transformConstraint', () => {
transform.transformConstraint({
$exists: {
$relativeTime: '12 days ago',
}
},
});
}).toThrow();
});
})
});
});
describe('relativeTimeToDate', () => {
@@ -417,7 +565,9 @@ describe('relativeTimeToDate', () => {
describe('Error cases', () => {
it('should error if string is completely gibberish', () => {
expect(transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({
expect(
transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')
).toEqual({
status: 'error',
info: "Time should either start with 'in' or end with 'ago'",
});
@@ -471,4 +621,3 @@ describe('relativeTimeToDate', () => {
});
});
});
+11 -10
View File
@@ -1,12 +1,13 @@
const NullCacheAdapter = require('../src/Adapters/Cache/NullCacheAdapter').default;
const NullCacheAdapter = require('../lib/Adapters/Cache/NullCacheAdapter')
.default;
describe('NullCacheAdapter', function() {
const KEY = 'hello';
const VALUE = 'world';
it('should expose promisifyed methods', (done) => {
it('should expose promisifyed methods', done => {
const cache = new NullCacheAdapter({
ttl: NaN
ttl: NaN,
});
// Verify all methods return promises.
@@ -14,24 +15,24 @@ describe('NullCacheAdapter', function() {
cache.put(KEY, VALUE),
cache.del(KEY),
cache.get(KEY),
cache.clear()
cache.clear(),
]).then(() => {
done();
});
});
it('should get/set/clear', (done) => {
it('should get/set/clear', done => {
const cache = new NullCacheAdapter({
ttl: NaN
ttl: NaN,
});
cache.put(KEY, VALUE)
cache
.put(KEY, VALUE)
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(null))
.then(value => expect(value).toEqual(null))
.then(() => cache.clear())
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(null))
.then(value => expect(value).toEqual(null))
.then(done);
});
});
+82 -69
View File
@@ -1,140 +1,153 @@
const OAuth = require("../src/Adapters/Auth/OAuth1Client");
const OAuth = require('../lib/Adapters/Auth/OAuth1Client');
describe('OAuth', function() {
it("Nonce should have right length", (done) => {
it('Nonce should have right length', done => {
jequal(OAuth.nonce().length, 30);
done();
});
it("Should properly build parameter string", (done) => {
const string = OAuth.buildParameterString({c:1, a:2, b:3})
jequal(string, "a=2&b=3&c=1");
it('Should properly build parameter string', done => {
const string = OAuth.buildParameterString({ c: 1, a: 2, b: 3 });
jequal(string, 'a=2&b=3&c=1');
done();
});
it("Should properly build empty parameter string", (done) => {
const string = OAuth.buildParameterString()
jequal(string, "");
it('Should properly build empty parameter string', done => {
const string = OAuth.buildParameterString();
jequal(string, '');
done();
});
it("Should properly build signature string", (done) => {
const string = OAuth.buildSignatureString("get", "http://dummy.com", "");
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
it('Should properly build signature string', done => {
const string = OAuth.buildSignatureString('get', 'http://dummy.com', '');
jequal(string, 'GET&http%3A%2F%2Fdummy.com&');
done();
});
it("Should properly generate request signature", (done) => {
it('Should properly generate request signature', done => {
let request = {
host: "dummy.com",
path: "path"
host: 'dummy.com',
path: 'path',
};
const oauth_params = {
oauth_timestamp: 123450000,
oauth_nonce: "AAAAAAAAAAAAAAAAA",
oauth_consumer_key: "hello",
oauth_token: "token"
oauth_nonce: 'AAAAAAAAAAAAAAAAA',
oauth_consumer_key: 'hello',
oauth_token: 'token',
};
const consumer_secret = "world";
const auth_token_secret = "secret";
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
const consumer_secret = 'world';
const auth_token_secret = 'secret';
request = OAuth.signRequest(
request,
oauth_params,
consumer_secret,
auth_token_secret
);
jequal(
request.headers['Authorization'],
'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"'
);
done();
});
it("Should properly build request", (done) => {
it('Should properly build request', done => {
const options = {
host: "dummy.com",
consumer_key: "hello",
consumer_secret: "world",
auth_token: "token",
auth_token_secret: "secret",
host: 'dummy.com',
consumer_key: 'hello',
consumer_secret: 'world',
auth_token: 'token',
auth_token_secret: 'secret',
// Custom oauth params for tests
oauth_params: {
oauth_timestamp: 123450000,
oauth_nonce: "AAAAAAAAAAAAAAAAA"
}
oauth_nonce: 'AAAAAAAAAAAAAAAAA',
},
};
const path = "path";
const method = "get";
const path = 'path';
const method = 'get';
const oauthClient = new OAuth(options);
const req = oauthClient.buildRequest(method, path, {"query": "param"});
const req = oauthClient.buildRequest(method, path, { query: 'param' });
jequal(req.host, options.host);
jequal(req.path, "/" + path + "?query=param");
jequal(req.method, "GET");
jequal(req.path, '/' + path + '?query=param');
jequal(req.method, 'GET');
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
jequal(
req.headers['Authorization'],
'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"'
);
done();
});
function validateCannotAuthenticateError(data, done) {
jequal(typeof data, "object");
jequal(typeof data.errors, "object");
jequal(typeof data, 'object');
jequal(typeof data.errors, 'object');
const errors = data.errors;
jequal(typeof errors[0], "object");
jequal(typeof errors[0], 'object');
// Cannot authenticate error
jequal(errors[0].code, 32);
done();
}
it("Should fail a GET request", (done) => {
it('Should fail a GET request', done => {
const options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
host: 'api.twitter.com',
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
};
const path = "/1.1/help/configuration.json";
const params = {"lang": "en"};
const path = '/1.1/help/configuration.json';
const params = { lang: 'en' };
const oauthClient = new OAuth(options);
oauthClient.get(path, params).then(function(data){
oauthClient.get(path, params).then(function(data) {
validateCannotAuthenticateError(data, done);
})
});
});
it("Should fail a POST request", (done) => {
it('Should fail a POST request', done => {
const options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
host: 'api.twitter.com',
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
};
const body = {
lang: "en"
lang: 'en',
};
const path = "/1.1/account/settings.json";
const path = '/1.1/account/settings.json';
const oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(data){
oauthClient.post(path, null, body).then(function(data) {
validateCannotAuthenticateError(data, done);
})
});
});
it("Should fail a request", (done) => {
it('Should fail a request', done => {
const options = {
host: "localhost",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
host: 'localhost',
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
};
const body = {
lang: "en"
lang: 'en',
};
const path = "/";
const path = '/';
const oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(){
jequal(false, true);
done();
}).catch(function(){
jequal(true, true);
done();
})
oauthClient
.post(path, null, body)
.then(function() {
jequal(false, true);
done();
})
.catch(function() {
jequal(true, true);
done();
});
});
it("Should fail with missing options", (done) => {
it('Should fail with missing options', done => {
const options = undefined;
try {
new OAuth(options);
+330 -256
View File
@@ -1,12 +1,12 @@
'use strict';
const request = require('request');
const request = require('../lib/request');
const delayPromise = (delay) => {
return new Promise((resolve) => {
const delayPromise = delay => {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
}
};
describe('Parse.Push', () => {
const setup = function() {
@@ -15,10 +15,10 @@ describe('Parse.Push', () => {
const pushAdapter = {
send: function(body, installations) {
const badge = body.data.badge;
const promises = installations.map((installation) => {
const promises = installations.map(installation => {
sendToInstallationSpy(installation);
if (installation.deviceType == "ios") {
if (installation.deviceType == 'ios') {
expect(installation.badge).toEqual(badge);
expect(installation.originalBadge + 1).toEqual(installation.badge);
} else {
@@ -27,33 +27,39 @@ describe('Parse.Push', () => {
return Promise.resolve({
err: null,
device: installation,
transmitted: true
})
transmitted: true,
});
});
return Promise.all(promises);
},
getValidPushTypes: function() {
return ["ios", "android"];
}
}
return ['ios', 'android'];
},
};
return reconfigureServer({
appId: Parse.applicationId,
masterKey: Parse.masterKey,
serverURL: Parse.serverURL,
push: {
adapter: pushAdapter
}
adapter: pushAdapter,
},
})
.then(() => {
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
while (installations.length != 10) {
const installation = new Parse.Object('_Installation');
installation.set(
'installationId',
'installation_' + installations.length
);
installation.set(
'deviceToken',
'device_token_' + installations.length
);
installation.set('badge', installations.length);
installation.set('originalBadge', installations.length);
installation.set('deviceType', 'ios');
installations.push(installation);
}
return Parse.Object.saveAll(installations);
@@ -63,83 +69,102 @@ describe('Parse.Push', () => {
sendToInstallationSpy,
};
})
.catch((err) => {
.catch(err => {
console.error(err);
throw err;
})
}
});
};
it('should properly send push', (done) => {
return setup().then(({ sendToInstallationSpy }) => {
return Parse.Push.send({
where: {
deviceType: 'ios'
},
data: {
badge: 'Increment',
alert: 'Hello world!'
}
}, {useMasterKey: true})
.then(() => {
return delayPromise(500);
})
.then(() => {
expect(sendToInstallationSpy.calls.count()).toEqual(10);
})
}).then(() => {
done();
}).catch((err) => {
jfail(err);
done();
});
it('should properly send push', done => {
return setup()
.then(({ sendToInstallationSpy }) => {
return Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'Increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
.then(() => {
return delayPromise(500);
})
.then(() => {
expect(sendToInstallationSpy.calls.count()).toEqual(10);
});
})
.then(() => {
done();
})
.catch(err => {
jfail(err);
done();
});
});
it('should properly send push with lowercaseIncrement', (done) => {
return setup().then(() => {
return Parse.Push.send({
where: {
deviceType: 'ios'
},
data: {
badge: 'increment',
alert: 'Hello world!'
}
}, {useMasterKey: true})
}).then(() => {
return delayPromise(500);
}).then(() => {
done();
}).catch((err) => {
jfail(err);
done();
});
it('should properly send push with lowercaseIncrement', done => {
return setup()
.then(() => {
return Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
);
})
.then(() => {
return delayPromise(500);
})
.then(() => {
done();
})
.catch(err => {
jfail(err);
done();
});
});
it('should not allow clients to query _PushStatus', done => {
setup()
.then(() => Parse.Push.send({
where: {
deviceType: 'ios'
},
data: {
badge: 'increment',
alert: 'Hello world!'
}
}, {useMasterKey: true}))
.then(() =>
Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
)
.then(() => delayPromise(500))
.then(() => {
request.get({
request({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
},
}, (error, response, body) => {
expect(body.error).toEqual('unauthorized');
}).then(fail, response => {
expect(response.data.error).toEqual('unauthorized');
done();
});
}).catch((err) => {
})
.catch(err => {
jfail(err);
done();
});
@@ -147,86 +172,106 @@ describe('Parse.Push', () => {
it('should allow master key to query _PushStatus', done => {
setup()
.then(() => Parse.Push.send({
where: {
deviceType: 'ios'
},
data: {
badge: 'increment',
alert: 'Hello world!'
}
}, {useMasterKey: true}))
.then(() =>
Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
)
.then(() => delayPromise(500)) // put a delay as we keep writing
.then(() => {
request.get({
request({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
}).then(response => {
const body = response.data;
try {
expect(body.results.length).toEqual(1);
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
} catch(e) {
expect(body.results[0].payload).toEqual(
'{"badge":"increment","alert":"Hello world!"}'
);
} catch (e) {
jfail(e);
}
done();
});
}).catch((err) => {
})
.catch(err => {
jfail(err);
done();
});
});
it('should throw error if missing push configuration', done => {
reconfigureServer({push: null})
reconfigureServer({ push: null })
.then(() => {
return Parse.Push.send({
where: {
deviceType: 'ios'
return Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
data: {
badge: 'increment',
alert: 'Hello world!'
}
}, {useMasterKey: true})
}).then(() => {
fail('should not succeed');
}, (err) => {
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
done();
}).catch((err) => {
{ useMasterKey: true }
);
})
.then(
() => {
fail('should not succeed');
},
err => {
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
done();
}
)
.catch(err => {
jfail(err);
done();
});
});
const successfulAny = function(body, installations) {
const promises = installations.map((device) => {
const promises = installations.map(device => {
return Promise.resolve({
transmitted: true,
device: device,
})
});
});
return Promise.all(promises);
};
const provideInstallations = function(num) {
if(!num) {
if (!num) {
num = 2;
}
const installations = [];
while(installations.length !== num) {
while (installations.length !== num) {
// add Android installations
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length);
installation.set("deviceType", "android");
const installation = new Parse.Object('_Installation');
installation.set(
'installationId',
'installation_' + installations.length
);
installation.set('deviceToken', 'device_token_' + installations.length);
installation.set('deviceType', 'android');
installations.push(installation);
}
@@ -242,8 +287,8 @@ describe('Parse.Push', () => {
return successfulAny(body, installations);
},
getValidPushTypes: function() {
return ["android"];
}
return ['android'];
},
};
/**
@@ -251,39 +296,44 @@ describe('Parse.Push', () => {
* Simulates a simple push where 1 installation is removed between _PushStatus
* count being set and the pushes being sent
*/
it('does not get stuck with _PushStatus \'running\' on 1 installation lost', (done) => {
it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => {
reconfigureServer({
push: {adapter: losingAdapter}
}).then(() => {
return Parse.Object.saveAll(provideInstallations());
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: 'android'}
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
push: { adapter: losingAdapter },
})
.then(() => {
return Parse.Object.saveAll(provideInstallations());
})
.then(() => {
return Parse.Push.send(
{
data: { alert: 'We fixed our status!' },
where: { deviceType: 'android' },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(1);
expect(result.get('count')).toEqual(undefined);
done();
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(1);
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
@@ -291,14 +341,17 @@ describe('Parse.Push', () => {
* Simulates a simple push where 1 installation is added between _PushStatus
* count being set and the pushes being sent
*/
it('does not get stuck with _PushStatus \'running\' on 1 installation added', (done) => {
it("does not get stuck with _PushStatus 'running' on 1 installation added", done => {
const installations = provideInstallations();
// add 1 iOS installation which we will omit & add later on
const iOSInstallation = new Parse.Object("_Installation");
iOSInstallation.set("installationId", "installation_" + installations.length);
iOSInstallation.set("deviceToken","device_token_" + installations.length);
iOSInstallation.set("deviceType", "ios");
const iOSInstallation = new Parse.Object('_Installation');
iOSInstallation.set(
'installationId',
'installation_' + installations.length
);
iOSInstallation.set('deviceToken', 'device_token_' + installations.length);
iOSInstallation.set('deviceType', 'ios');
installations.push(iOSInstallation);
reconfigureServer({
@@ -312,40 +365,45 @@ describe('Parse.Push', () => {
return successfulAny(body, installations);
},
getValidPushTypes: function() {
return ["android"];
}
}
}
}).then(() => {
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: {'$ne' : 'random'}}
return ['android'];
},
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
},
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(() => {
return Parse.Push.send(
{
data: { alert: 'We fixed our status!' },
where: { deviceType: { $ne: 'random' } },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(3);
expect(result.get('count')).toEqual(undefined);
done();
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(3);
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
@@ -353,43 +411,48 @@ describe('Parse.Push', () => {
* Simulates an extended push, where some installations may be removed,
* resulting in a non-zero count
*/
it('does not get stuck with _PushStatus \'running\' on many installations removed', (done) => {
it("does not get stuck with _PushStatus 'running' on many installations removed", done => {
const devices = 1000;
const installations = provideInstallations(devices);
reconfigureServer({
push: {adapter: losingAdapter}
}).then(() => {
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: 'android'}
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
push: { adapter: losingAdapter },
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(() => {
return Parse.Push.send(
{
data: { alert: 'We fixed our status!' },
where: { deviceType: 'android' },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices - devices / 100);
expect(result.get('count')).toEqual(undefined);
done();
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices - (devices / 100));
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
@@ -397,18 +460,24 @@ describe('Parse.Push', () => {
* Simulates an extended push, where some installations may be added,
* resulting in a non-zero count
*/
it('does not get stuck with _PushStatus \'running\' on many installations added', (done) => {
it("does not get stuck with _PushStatus 'running' on many installations added", done => {
const devices = 1000;
const installations = provideInstallations(devices);
// add 1 iOS installation which we will omit & add later on
const iOSInstallations = [];
while(iOSInstallations.length !== (devices / 100)) {
const iOSInstallation = new Parse.Object("_Installation");
iOSInstallation.set("installationId", "installation_" + installations.length);
iOSInstallation.set("deviceToken", "device_token_" + installations.length);
iOSInstallation.set("deviceType", "ios");
while (iOSInstallations.length !== devices / 100) {
const iOSInstallation = new Parse.Object('_Installation');
iOSInstallation.set(
'installationId',
'installation_' + installations.length
);
iOSInstallation.set(
'deviceToken',
'device_token_' + installations.length
);
iOSInstallation.set('deviceType', 'ios');
installations.push(iOSInstallation);
iOSInstallations.push(iOSInstallation);
}
@@ -424,40 +493,45 @@ describe('Parse.Push', () => {
return successfulAny(body, installations);
},
getValidPushTypes: function() {
return ["android"];
}
}
}
}).then(() => {
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: {'$ne' : 'random'}}
return ['android'];
},
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
},
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(() => {
return Parse.Push.send(
{
data: { alert: 'We fixed our status!' },
where: { deviceType: { $ne: 'random' } },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices + devices / 100);
expect(result.get('count')).toEqual(undefined);
done();
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices + (devices / 100));
expect(result.get('count')).toEqual(undefined);
done();
});
});
});
+870 -1201
View File
File diff suppressed because it is too large Load Diff
+1039 -798
View File
File diff suppressed because it is too large Load Diff
+23 -12
View File
@@ -1,4 +1,5 @@
const ParseCloudCodePublisher = require('../src/LiveQuery/ParseCloudCodePublisher').ParseCloudCodePublisher;
const ParseCloudCodePublisher = require('../lib/LiveQuery/ParseCloudCodePublisher')
.ParseCloudCodePublisher;
const Parse = require('parse/node');
describe('ParseCloudCodePublisher', function() {
@@ -7,22 +8,26 @@ describe('ParseCloudCodePublisher', function() {
const mockParsePubSub = {
createPublisher: jasmine.createSpy('publish').and.returnValue({
publish: jasmine.createSpy('publish'),
on: jasmine.createSpy('on')
on: jasmine.createSpy('on'),
}),
createSubscriber: jasmine.createSpy('publish').and.returnValue({
subscribe: jasmine.createSpy('subscribe'),
on: jasmine.createSpy('on')
})
on: jasmine.createSpy('on'),
}),
};
jasmine.mockLibrary('../src/LiveQuery/ParsePubSub', 'ParsePubSub', mockParsePubSub);
jasmine.mockLibrary(
'../lib/LiveQuery/ParsePubSub',
'ParsePubSub',
mockParsePubSub
);
done();
});
it('can initialize', function() {
const config = {}
const config = {};
new ParseCloudCodePublisher(config);
const ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
const ParsePubSub = require('../lib/LiveQuery/ParsePubSub').ParsePubSub;
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
});
@@ -32,7 +37,10 @@ describe('ParseCloudCodePublisher', function() {
const request = {};
publisher.onCloudCodeAfterSave(request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterSave', request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(
Parse.applicationId + 'afterSave',
request
);
});
it('can handle cloud code afterDelete request', function() {
@@ -41,7 +49,10 @@ describe('ParseCloudCodePublisher', function() {
const request = {};
publisher.onCloudCodeAfterDelete(request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterDelete', request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(
Parse.applicationId + 'afterDelete',
request
);
});
it('can handle cloud code request', function() {
@@ -52,7 +63,7 @@ describe('ParseCloudCodePublisher', function() {
originalParseObject.set('key', 'originalValue');
const request = {
object: currentParseObject,
original: originalParseObject
original: originalParseObject,
};
publisher._onCloudCodeMessage('afterSave', request);
@@ -63,7 +74,7 @@ describe('ParseCloudCodePublisher', function() {
expect(message.originalParseObject).toEqual(request.original._toFullJSON());
});
afterEach(function(){
jasmine.restoreLibrary('../src/LiveQuery/ParsePubSub', 'ParsePubSub');
afterEach(function() {
jasmine.restoreLibrary('../lib/LiveQuery/ParsePubSub', 'ParsePubSub');
});
});
+456 -469
View File
File diff suppressed because it is too large Load Diff
+660 -514
View File
File diff suppressed because it is too large Load Diff
+130 -114
View File
@@ -1,74 +1,88 @@
'use strict';
const request = require('request');
const Config = require('../src/Config');
const request = require('../lib/request');
const Config = require('../lib/Config');
describe('a GlobalConfig', () => {
beforeEach(done => {
const config = Config.get('test');
const query = on_db('mongo', () => {
// Legacy is with an int...
return { objectId: 1 };
}, () => {
return { objectId: "1" }
})
config.database.adapter.upsertOneObject(
'_GlobalConfig',
{ fields: { objectId: { type: 'Number' }, params: {type: 'Object'}} },
query,
{ params: { companies: ['US', 'DK'] } }
).then(done, (err) => {
jfail(err);
done();
});
const query = on_db(
'mongo',
() => {
// Legacy is with an int...
return { objectId: 1 };
},
() => {
return { objectId: '1' };
}
);
config.database.adapter
.upsertOneObject(
'_GlobalConfig',
{
fields: { objectId: { type: 'Number' }, params: { type: 'Object' } },
},
query,
{ params: { companies: ['US', 'DK'] } }
)
.then(done, err => {
jfail(err);
done();
});
});
it('can be retrieved', (done) => {
request.get({
url : 'http://localhost:8378/1/config',
json : true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
};
it('can be retrieved', done => {
request({
url: 'http://localhost:8378/1/config',
json: true,
headers,
}).then(response => {
const body = response.data;
try {
expect(response.statusCode).toEqual(200);
expect(response.status).toEqual(200);
expect(body.params.companies).toEqual(['US', 'DK']);
} catch(e) { jfail(e); }
} catch (e) {
jfail(e);
}
done();
});
});
it('can be updated when a master key exists', (done) => {
request.put({
url : 'http://localhost:8378/1/config',
json : true,
body : { params: { companies: ['US', 'DK', 'SE'] } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
it('can be updated when a master key exists', done => {
request({
method: 'PUT',
url: 'http://localhost:8378/1/config',
json: true,
body: { params: { companies: ['US', 'DK', 'SE'] } },
headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
expect(body.result).toEqual(true);
done();
});
});
it('can add and retrive files', (done) => {
request.put({
url : 'http://localhost:8378/1/config',
json : true,
body : { params: { file: { __type: 'File', name: 'name', url: 'http://url' } } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
it('can add and retrive files', done => {
request({
method: 'PUT',
url: 'http://localhost:8378/1/config',
json: true,
body: {
params: { file: { __type: 'File', name: 'name', url: 'http://url' } },
},
headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
expect(body.result).toEqual(true);
Parse.Config.get().then((res) => {
Parse.Config.get().then(res => {
const file = res.get('file');
expect(file.name()).toBe('name');
expect(file.url()).toBe('http://url');
@@ -77,20 +91,19 @@ describe('a GlobalConfig', () => {
});
});
it('can add and retrive Geopoints', (done) => {
const geopoint = new Parse.GeoPoint(10,-20);
request.put({
url : 'http://localhost:8378/1/config',
json : true,
body : { params: { point: geopoint.toJSON() } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
it('can add and retrive Geopoints', done => {
const geopoint = new Parse.GeoPoint(10, -20);
request({
method: 'PUT',
url: 'http://localhost:8378/1/config',
json: true,
body: { params: { point: geopoint.toJSON() } },
headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
expect(body.result).toEqual(true);
Parse.Config.get().then((res) => {
Parse.Config.get().then(res => {
const point = res.get('point');
expect(point.latitude).toBe(10);
expect(point.longitude).toBe(-20);
@@ -99,75 +112,78 @@ describe('a GlobalConfig', () => {
});
});
it('properly handles delete op', (done) => {
request.put({
url : 'http://localhost:8378/1/config',
json : true,
body : { params: { companies: {__op: 'Delete'}, foo: 'bar' } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
it('properly handles delete op', done => {
request({
method: 'PUT',
url: 'http://localhost:8378/1/config',
json: true,
body: { params: { companies: { __op: 'Delete' }, foo: 'bar' } },
headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
expect(body.result).toEqual(true);
request.get({
url : 'http://localhost:8378/1/config',
json : true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
request({
url: 'http://localhost:8378/1/config',
json: true,
headers,
}).then(response => {
const body = response.data;
try {
expect(response.statusCode).toEqual(200);
expect(response.status).toEqual(200);
expect(body.params.companies).toBeUndefined();
expect(body.params.foo).toBe('bar');
expect(Object.keys(body.params).length).toBe(1);
} catch(e) { jfail(e); }
} catch (e) {
jfail(e);
}
done();
});
});
});
it('fail to update if master key is missing', (done) => {
request.put({
url : 'http://localhost:8378/1/config',
json : true,
body : { params: { companies: [] } },
it('fail to update if master key is missing', done => {
request({
method: 'PUT',
url: 'http://localhost:8378/1/config',
json: true,
body: { params: { companies: [] } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key' : 'rest'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(403);
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
}).then(fail, response => {
const body = response.data;
expect(response.status).toEqual(403);
expect(body.error).toEqual('unauthorized: master key is required');
done();
});
});
it('failed getting config when it is missing', (done) => {
it('failed getting config when it is missing', done => {
const config = Config.get('test');
config.database.adapter.deleteObjectsByQuery(
'_GlobalConfig',
{ fields: { params: { __type: 'String' } } },
{ objectId: "1" }
).then(() => {
request.get({
url : 'http://localhost:8378/1/config',
json : true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key' : 'test'
}
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
expect(body.params).toEqual({});
config.database.adapter
.deleteObjectsByQuery(
'_GlobalConfig',
{ fields: { params: { __type: 'String' } } },
{ objectId: '1' }
)
.then(() => {
request({
url: 'http://localhost:8378/1/config',
json: true,
headers,
}).then(response => {
const body = response.data;
expect(response.status).toEqual(200);
expect(body.params).toEqual({});
done();
});
})
.catch(e => {
jfail(e);
done();
});
}).catch((e) => {
jfail(e);
done();
});
});
});
+973
View File
@@ -0,0 +1,973 @@
const {
default: ParseGraphQLController,
GraphQLConfigClassName,
GraphQLConfigId,
GraphQLConfigKey,
} = require('../lib/Controllers/ParseGraphQLController');
const { isEqual } = require('lodash');
describe('ParseGraphQLController', () => {
let parseServer;
let databaseController;
let cacheController;
let databaseUpdateArgs;
// Holds the graphQLConfig in memory instead of using the db
let graphQLConfigRecord;
const setConfigOnDb = graphQLConfigData => {
graphQLConfigRecord = {
objectId: GraphQLConfigId,
[GraphQLConfigKey]: graphQLConfigData,
};
};
const removeConfigFromDb = () => {
graphQLConfigRecord = null;
};
const getConfigFromDb = () => {
return graphQLConfigRecord;
};
beforeAll(async () => {
parseServer = await global.reconfigureServer({
schemaCacheTTL: 100,
});
databaseController = parseServer.config.databaseController;
cacheController = parseServer.config.cacheController;
const defaultFind = databaseController.find.bind(databaseController);
databaseController.find = async (className, query, ...args) => {
if (
className === GraphQLConfigClassName &&
isEqual(query, { objectId: GraphQLConfigId })
) {
const graphQLConfigRecord = getConfigFromDb();
return graphQLConfigRecord ? [graphQLConfigRecord] : [];
} else {
return defaultFind(className, query, ...args);
}
};
const defaultUpdate = databaseController.update.bind(databaseController);
databaseController.update = async (
className,
query,
update,
fullQueryOptions
) => {
databaseUpdateArgs = [className, query, update, fullQueryOptions];
if (
className === GraphQLConfigClassName &&
isEqual(query, { objectId: GraphQLConfigId }) &&
update &&
!!update[GraphQLConfigKey] &&
fullQueryOptions &&
isEqual(fullQueryOptions, { upsert: true })
) {
setConfigOnDb(update[GraphQLConfigKey]);
} else {
return defaultUpdate(...databaseUpdateArgs);
}
};
});
beforeEach(() => {
databaseUpdateArgs = null;
});
describe('constructor', () => {
it('should require a databaseController', () => {
expect(() => new ParseGraphQLController()).toThrow(
'ParseGraphQLController requires a "databaseController" to be instantiated.'
);
expect(() => new ParseGraphQLController({ cacheController })).toThrow(
'ParseGraphQLController requires a "databaseController" to be instantiated.'
);
expect(
() =>
new ParseGraphQLController({
cacheController,
mountGraphQL: false,
})
).toThrow(
'ParseGraphQLController requires a "databaseController" to be instantiated.'
);
});
it('should construct without a cacheController', () => {
expect(
() =>
new ParseGraphQLController({
databaseController,
})
).not.toThrow();
expect(
() =>
new ParseGraphQLController({
databaseController,
mountGraphQL: true,
})
).not.toThrow();
});
it('should set isMounted to true if config.mountGraphQL is true', () => {
const mountedController = new ParseGraphQLController({
databaseController,
mountGraphQL: true,
});
expect(mountedController.isMounted).toBe(true);
const unmountedController = new ParseGraphQLController({
databaseController,
mountGraphQL: false,
});
expect(unmountedController.isMounted).toBe(false);
const unmountedController2 = new ParseGraphQLController({
databaseController,
});
expect(unmountedController2.isMounted).toBe(false);
});
});
describe('getGraphQLConfig', () => {
it('should return an empty graphQLConfig if collection has none', async () => {
removeConfigFromDb();
const parseGraphQLController = new ParseGraphQLController({
databaseController,
mountGraphQL: false,
});
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
expect(graphQLConfig).toEqual({});
});
it('should return an existing graphQLConfig', async () => {
setConfigOnDb({ enabledForClasses: ['_User'] });
const parseGraphQLController = new ParseGraphQLController({
databaseController,
mountGraphQL: false,
});
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
expect(graphQLConfig).toEqual({ enabledForClasses: ['_User'] });
});
it('should use the cache if mounted, and return the stored graphQLConfig', async () => {
removeConfigFromDb();
cacheController.graphQL.clear();
const parseGraphQLController = new ParseGraphQLController({
databaseController,
cacheController,
mountGraphQL: true,
});
cacheController.graphQL.put(parseGraphQLController.configCacheKey, {
enabledForClasses: ['SuperCar'],
});
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
expect(graphQLConfig).toEqual({ enabledForClasses: ['SuperCar'] });
});
it('should use the database when mounted and cache is empty', async () => {
setConfigOnDb({ disabledForClasses: ['SuperCar'] });
cacheController.graphQL.clear();
const parseGraphQLController = new ParseGraphQLController({
databaseController,
cacheController,
mountGraphQL: true,
});
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
expect(graphQLConfig).toEqual({ disabledForClasses: ['SuperCar'] });
});
it('should store the graphQLConfig in cache if mounted', async () => {
setConfigOnDb({ enabledForClasses: ['SuperCar'] });
cacheController.graphQL.clear();
const parseGraphQLController = new ParseGraphQLController({
databaseController,
cacheController,
mountGraphQL: true,
});
const cachedValueBefore = await cacheController.graphQL.get(
parseGraphQLController.configCacheKey
);
expect(cachedValueBefore).toBeNull();
await parseGraphQLController.getGraphQLConfig();
const cachedValueAfter = await cacheController.graphQL.get(
parseGraphQLController.configCacheKey
);
expect(cachedValueAfter).toEqual({ enabledForClasses: ['SuperCar'] });
});
});
describe('updateGraphQLConfig', () => {
const successfulUpdateResponse = { response: { result: true } };
it('should throw if graphQLConfig is not provided', async function() {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig()
).toBeRejectedWith('You must provide a graphQLConfig!');
});
it('should correct update the graphQLConfig object using the databaseController', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
const graphQLConfig = {
enabledForClasses: ['ClassA', 'ClassB'],
disabledForClasses: [],
classConfigs: [
{ className: 'ClassA', query: { get: false } },
{ className: 'ClassB', mutation: { destroy: false }, type: {} },
],
};
await parseGraphQLController.updateGraphQLConfig(graphQLConfig);
expect(databaseUpdateArgs).toBeTruthy();
const [className, query, update, op] = databaseUpdateArgs;
expect(className).toBe(GraphQLConfigClassName);
expect(query).toEqual({ objectId: GraphQLConfigId });
expect(update).toEqual({
[GraphQLConfigKey]: graphQLConfig,
});
expect(op).toEqual({ upsert: true });
});
it('should throw if graphQLConfig is not an object', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig([])
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig(function() {})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig(Promise.resolve({}))
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig('')
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if graphQLConfig has an invalid root key', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({ invalidKey: true })
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if graphQLConfig has invalid class filters', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({ enabledForClasses: {} })
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
enabledForClasses: [undefined],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
disabledForClasses: [null],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
enabledForClasses: ['_User', null],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({ disabledForClasses: [''] })
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
enabledForClasses: [],
disabledForClasses: ['_User'],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if classConfigs array is invalid', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({ classConfigs: {} })
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({ classConfigs: [null] })
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [undefined],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [{ className: 'ValidClass' }, null],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({ classConfigs: [] })
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid type settings', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: [],
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
invalidKey: true,
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid type.inputFields settings', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: 'SuperCar',
type: {
inputFields: [],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: 'SuperCar',
type: {
inputFields: {
invalidKey: true,
},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: 'SuperCar',
type: {
inputFields: {
create: {},
},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: 'SuperCar',
type: {
inputFields: {
update: [null],
},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: 'SuperCar',
type: {
inputFields: {
create: [],
update: [],
},
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: 'SuperCar',
type: {
inputFields: {
create: ['make', 'model'],
update: [],
},
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid type.outputFields settings', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
outputFields: {},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
outputFields: [null],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
outputFields: ['name', undefined],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
outputFields: [''],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
outputFields: [],
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
outputFields: ['name'],
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid type.constraintFields settings', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
constraintFields: {},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
constraintFields: [null],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
constraintFields: ['name', undefined],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
constraintFields: [''],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
constraintFields: [],
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
constraintFields: ['name'],
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid type.sortFields settings', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: {},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [null],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [
{
field: undefined,
asc: true,
desc: true,
},
],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [
{
field: '',
asc: true,
desc: false,
},
],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [
{
field: 'name',
asc: true,
desc: 'false',
},
],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [
{
field: 'name',
asc: true,
desc: true,
},
null,
],
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [],
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
sortFields: [
{
field: 'name',
asc: true,
desc: true,
},
],
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid query params', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
query: [],
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
query: {
invalidKey: true,
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
query: {
get: 1,
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
query: {
find: 'true',
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
query: {
get: false,
find: true,
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
query: {},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if a classConfig has invalid mutation params', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
mutation: [],
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
mutation: {
invalidKey: true,
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
mutation: {
destroy: 1,
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
mutation: {
update: 'true',
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
mutation: {},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
mutation: {
create: true,
update: true,
destroy: false,
},
},
],
})
).toBeResolvedTo(successfulUpdateResponse);
});
it('should throw if _User create fields is missing username or password', async () => {
const parseGraphQLController = new ParseGraphQLController({
databaseController,
});
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
inputFields: {
create: ['username', 'no-password'],
},
},
},
],
})
).toBeRejected();
expectAsync(
parseGraphQLController.updateGraphQLConfig({
classConfigs: [
{
className: '_User',
type: {
inputFields: {
create: ['username', 'password'],
},
},
},
],
})
).toBeResolved(successfulUpdateResponse);
});
it('should update the cache if mounted', async () => {
removeConfigFromDb();
cacheController.graphQL.clear();
const mountedController = new ParseGraphQLController({
databaseController,
cacheController,
mountGraphQL: true,
});
const unmountedController = new ParseGraphQLController({
databaseController,
cacheController,
mountGraphQL: false,
});
let cacheBeforeValue;
let cacheAfterValue;
cacheBeforeValue = await cacheController.graphQL.get(
mountedController.configCacheKey
);
expect(cacheBeforeValue).toBeNull();
await mountedController.updateGraphQLConfig({
enabledForClasses: ['SuperCar'],
});
cacheAfterValue = await cacheController.graphQL.get(
mountedController.configCacheKey
);
expect(cacheAfterValue).toEqual({ enabledForClasses: ['SuperCar'] });
// reset
removeConfigFromDb();
cacheController.graphQL.clear();
cacheBeforeValue = await cacheController.graphQL.get(
unmountedController.configCacheKey
);
expect(cacheBeforeValue).toBeNull();
await unmountedController.updateGraphQLConfig({
enabledForClasses: ['SuperCar'],
});
cacheAfterValue = await cacheController.graphQL.get(
unmountedController.configCacheKey
);
expect(cacheAfterValue).toBeNull();
});
});
});
+121
View File
@@ -0,0 +1,121 @@
const defaultLogger = require('../lib/logger').default;
const { ParseGraphQLSchema } = require('../lib/GraphQL/ParseGraphQLSchema');
describe('ParseGraphQLSchema', () => {
let parseServer;
let databaseController;
let parseGraphQLController;
let parseGraphQLSchema;
beforeAll(async () => {
parseServer = await global.reconfigureServer({
schemaCacheTTL: 100,
});
databaseController = parseServer.config.databaseController;
parseGraphQLController = parseServer.config.parseGraphQLController;
parseGraphQLSchema = new ParseGraphQLSchema({
databaseController,
parseGraphQLController,
log: defaultLogger,
});
});
describe('constructor', () => {
it('should require a parseGraphQLController, databaseController and a log instance', () => {
expect(() => new ParseGraphQLSchema()).toThrow(
'You must provide a parseGraphQLController instance!'
);
expect(
() => new ParseGraphQLSchema({ parseGraphQLController: {} })
).toThrow('You must provide a databaseController instance!');
expect(
() =>
new ParseGraphQLSchema({
parseGraphQLController: {},
databaseController: {},
})
).toThrow('You must provide a log instance!');
});
});
describe('load', () => {
it('should cache schema', async () => {
const graphQLSchema = await parseGraphQLSchema.load();
const updatedGraphQLSchema = await parseGraphQLSchema.load();
expect(graphQLSchema).toBe(updatedGraphQLSchema);
await new Promise(resolve => setTimeout(resolve, 200));
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
});
it('should load a brand new GraphQL Schema if Parse Schema changes', async () => {
await parseGraphQLSchema.load();
const parseClasses = parseGraphQLSchema.parseClasses;
const parseClassesString = parseGraphQLSchema.parseClassesString;
const parseClassTypes = parseGraphQLSchema.parseClassTypes;
const graphQLSchema = parseGraphQLSchema.graphQLSchema;
const graphQLTypes = parseGraphQLSchema.graphQLTypes;
const graphQLQueries = parseGraphQLSchema.graphQLQueries;
const graphQLMutations = parseGraphQLSchema.graphQLMutations;
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
const newClassObject = new Parse.Object('NewClass');
await newClassObject.save();
await databaseController.schemaCache.clear();
await new Promise(resolve => setTimeout(resolve, 200));
await parseGraphQLSchema.load();
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
expect(parseClassesString).not.toBe(
parseGraphQLSchema.parseClassesString
);
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
expect(graphQLQueries).not.toBe(parseGraphQLSchema.graphQLQueries);
expect(graphQLMutations).not.toBe(parseGraphQLSchema.graphQLMutations);
expect(graphQLSubscriptions).not.toBe(
parseGraphQLSchema.graphQLSubscriptions
);
});
it('should load a brand new GraphQL Schema if graphQLConfig changes', async () => {
const parseGraphQLController = {
graphQLConfig: { enabledForClasses: [] },
getGraphQLConfig() {
return this.graphQLConfig;
},
};
const parseGraphQLSchema = new ParseGraphQLSchema({
databaseController,
parseGraphQLController,
log: defaultLogger,
});
await parseGraphQLSchema.load();
const parseClasses = parseGraphQLSchema.parseClasses;
const parseClassesString = parseGraphQLSchema.parseClassesString;
const parseClassTypes = parseGraphQLSchema.parseClassTypes;
const graphQLSchema = parseGraphQLSchema.graphQLSchema;
const graphQLTypes = parseGraphQLSchema.graphQLTypes;
const graphQLQueries = parseGraphQLSchema.graphQLQueries;
const graphQLMutations = parseGraphQLSchema.graphQLMutations;
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
parseGraphQLController.graphQLConfig = {
enabledForClasses: ['_User'],
};
await new Promise(resolve => setTimeout(resolve, 200));
await parseGraphQLSchema.load();
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
expect(parseClassesString).not.toBe(
parseGraphQLSchema.parseClassesString
);
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
expect(graphQLQueries).not.toBe(parseGraphQLSchema.graphQLQueries);
expect(graphQLMutations).not.toBe(parseGraphQLSchema.graphQLMutations);
expect(graphQLSubscriptions).not.toBe(
parseGraphQLSchema.graphQLSubscriptions
);
});
});
});
File diff suppressed because it is too large Load Diff
+683 -413
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1675 -1572
View File
File diff suppressed because it is too large Load Diff
+358 -206
View File
@@ -1,264 +1,416 @@
const TestObject = Parse.Object.extend('TestObject');
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const rp = require('request-promise');
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
.default;
const mongoURI =
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const request = require('../lib/request');
const defaultHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest'
}
'X-Parse-Rest-API-Key': 'rest',
'Content-Type': 'application/json',
};
describe('Parse.Polygon testing', () => {
it('polygon save open path', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0]];
const closed = [[0,0],[0,1],[1,1],[1,0],[0,0]];
beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently());
it('polygon save open path', done => {
const coords = [[0, 0], [0, 1], [1, 1], [1, 0]];
const closed = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closed);
done();
}, done.fail);
return obj
.save()
.then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
})
.then(result => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closed);
done();
}, done.fail);
});
it('polygon save closed path', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
it('polygon save closed path', done => {
const coords = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, coords);
done();
}, done.fail);
return obj
.save()
.then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
})
.then(result => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, coords);
done();
}, done.fail);
});
it('polygon equalTo (open/closed) path', (done) => {
const openPoints = [[0,0],[0,1],[1,1],[1,0]];
const closedPoints = [[0,0],[0,1],[1,1],[1,0],[0,0]];
it('polygon equalTo (open/closed) path', done => {
const openPoints = [[0, 0], [0, 1], [1, 1], [1, 0]];
const closedPoints = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
const openPolygon = new Parse.Polygon(openPoints);
const closedPolygon = new Parse.Polygon(closedPoints);
const obj = new TestObject();
obj.set('polygon', openPolygon);
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
query.equalTo('polygon', openPolygon);
return query.find();
}).then((results) => {
const polygon = results[0].get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closedPoints);
const query = new Parse.Query(TestObject);
query.equalTo('polygon', closedPolygon);
return query.find();
}).then((results) => {
const polygon = results[0].get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closedPoints);
done();
}, done.fail);
return obj
.save()
.then(() => {
const query = new Parse.Query(TestObject);
query.equalTo('polygon', openPolygon);
return query.find();
})
.then(results => {
const polygon = results[0].get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closedPoints);
const query = new Parse.Query(TestObject);
query.equalTo('polygon', closedPolygon);
return query.find();
})
.then(results => {
const polygon = results[0].get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closedPoints);
done();
}, done.fail);
});
it('polygon update', (done) => {
const oldCoords = [[0,0],[0,1],[1,1],[1,0]];
it('polygon update', done => {
const oldCoords = [[0, 0], [0, 1], [1, 1], [1, 0]];
const oldPolygon = new Parse.Polygon(oldCoords);
const newCoords = [[2,2],[2,3],[3,3],[3,2]];
const newCoords = [[2, 2], [2, 3], [3, 3], [3, 2]];
const newPolygon = new Parse.Polygon(newCoords);
const obj = new TestObject();
obj.set('polygon', oldPolygon);
return obj.save().then(() => {
obj.set('polygon', newPolygon);
return obj.save();
}).then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
newCoords.push(newCoords[0]);
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, newCoords);
done();
}, done.fail);
return obj
.save()
.then(() => {
obj.set('polygon', newPolygon);
return obj.save();
})
.then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
})
.then(result => {
const polygon = result.get('polygon');
newCoords.push(newCoords[0]);
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, newCoords);
done();
}, done.fail);
});
it('polygon invalid value', (done) => {
const coords = [['foo','bar'],[0,1],[1,0],[1,1],[0,0]];
it('polygon invalid value', done => {
const coords = [['foo', 'bar'], [0, 1], [1, 0], [1, 1], [0, 0]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then(done.fail, done);
obj.set('polygon', { __type: 'Polygon', coordinates: coords });
return obj
.save()
.then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
})
.then(done.fail, () => done());
});
it('polygon three points minimum', (done) => {
const coords = [[0,0]];
it('polygon three points minimum', done => {
const coords = [[0, 0]];
const obj = new TestObject();
// use raw so we test the server validates properly
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(done.fail, done);
obj.set('polygon', { __type: 'Polygon', coordinates: coords });
obj.save().then(done.fail, () => done());
});
it('polygon three different points minimum', (done) => {
const coords = [[0,0],[0,1],[0,0]];
it('polygon three different points minimum', done => {
const coords = [[0, 0], [0, 1], [0, 0]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
obj.save().then(done.fail, done);
obj.save().then(done.fail, () => done());
});
it('polygon counterclockwise', (done) => {
const coords = [[1,1],[0,1],[0,0],[1,0]];
const closed = [[1,1],[0,1],[0,0],[1,0],[1,1]];
it('polygon counterclockwise', done => {
const coords = [[1, 1], [0, 1], [0, 0], [1, 0]];
const closed = [[1, 1], [0, 1], [0, 0], [1, 0], [1, 1]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closed);
done();
}, done.fail);
obj
.save()
.then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
})
.then(result => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closed);
done();
}, done.fail);
});
it('polygonContain query', (done) => {
const points1 = [[0,0],[0,1],[1,1],[1,0]];
const points2 = [[0,0],[0,2],[2,2],[2,0]];
const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]];
const polygon1 = new Parse.Polygon(points1);
const polygon2 = new Parse.Polygon(points2);
const polygon3 = new Parse.Polygon(points3);
const obj1 = new TestObject({location: polygon1});
const obj2 = new TestObject({location: polygon2});
const obj3 = new TestObject({location: polygon3});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 }
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
expect(resp.results.length).toBe(2);
done();
}, done.fail);
});
describe('with location', () => {
beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently());
it('polygonContain invalid input', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = new Parse.Polygon(points);
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 }
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then(done.fail, () => done());
});
it('polygonContain query', done => {
const points1 = [[0, 0], [0, 1], [1, 1], [1, 0]];
const points2 = [[0, 0], [0, 2], [2, 2], [2, 0]];
const points3 = [[10, 10], [10, 15], [15, 15], [15, 10], [10, 10]];
const polygon1 = new Parse.Polygon(points1);
const polygon2 = new Parse.Polygon(points2);
const polygon3 = new Parse.Polygon(points3);
const obj1 = new TestObject({ location: polygon1 });
const obj2 = new TestObject({ location: polygon2 });
const obj3 = new TestObject({ location: polygon3 });
Parse.Object.saveAll([obj1, obj2, obj3])
.then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 },
},
},
};
return request({
method: 'POST',
url: Parse.serverURL + '/classes/TestObject',
body: { where, _method: 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey,
'Content-Type': 'application/json',
},
});
})
.then(resp => {
expect(resp.data.results.length).toBe(2);
done();
}, done.fail);
});
it('polygonContain invalid geoPoint', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = new Parse.Polygon(points);
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: []
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then(done.fail, () => done());
it('polygonContain query no reverse input (Regression test for #4608)', done => {
const points1 = [[0.25, 0], [0.25, 1.25], [0.75, 1.25], [0.75, 0]];
const points2 = [[0, 0], [0, 2], [2, 2], [2, 0]];
const points3 = [[10, 10], [10, 15], [15, 15], [15, 10], [10, 10]];
const polygon1 = new Parse.Polygon(points1);
const polygon2 = new Parse.Polygon(points2);
const polygon3 = new Parse.Polygon(points3);
const obj1 = new TestObject({ location: polygon1 });
const obj2 = new TestObject({ location: polygon2 });
const obj3 = new TestObject({ location: polygon3 });
Parse.Object.saveAll([obj1, obj2, obj3])
.then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 1.0 },
},
},
};
return request({
method: 'POST',
url: Parse.serverURL + '/classes/TestObject',
body: { where, _method: 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey,
'Content-Type': 'application/json',
},
});
})
.then(resp => {
expect(resp.data.results.length).toBe(2);
done();
}, done.fail);
});
it('polygonContain query real data (Regression test for #4608)', done => {
const detroit = [
[42.631655189280224, -83.78406753121705],
[42.633047793854814, -83.75333640366955],
[42.61625254348911, -83.75149921669944],
[42.61526926650296, -83.78161794858735],
[42.631655189280224, -83.78406753121705],
];
const polygon = new Parse.Polygon(detroit);
const obj = new TestObject({ location: polygon });
obj
.save()
.then(() => {
const where = {
location: {
$geoIntersects: {
$point: {
__type: 'GeoPoint',
latitude: 42.624599,
longitude: -83.770162,
},
},
},
};
return request({
method: 'POST',
url: Parse.serverURL + '/classes/TestObject',
body: { where, _method: 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey,
'Content-Type': 'application/json',
},
});
})
.then(resp => {
expect(resp.data.results.length).toBe(1);
done();
}, done.fail);
});
it('polygonContain invalid input', done => {
const points = [[0, 0], [0, 1], [1, 1], [1, 0]];
const polygon = new Parse.Polygon(points);
const obj = new TestObject({ location: polygon });
obj
.save()
.then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 },
},
},
};
return request({
method: 'POST',
url: Parse.serverURL + '/classes/TestObject',
body: { where, _method: 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey,
},
});
})
.then(done.fail, () => done());
});
it('polygonContain invalid geoPoint', done => {
const points = [[0, 0], [0, 1], [1, 1], [1, 0]];
const polygon = new Parse.Polygon(points);
const obj = new TestObject({ location: polygon });
obj
.save()
.then(() => {
const where = {
location: {
$geoIntersects: {
$point: [],
},
},
};
return request({
method: 'POST',
url: Parse.serverURL + '/classes/TestObject',
body: { where, _method: 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey,
},
});
})
.then(done.fail, () => done());
});
});
});
describe_only_db('mongo')('Parse.Polygon testing', () => {
it('support 2d and 2dsphere', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently());
it('support 2d and 2dsphere', done => {
const coords = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
// testings against REST API, use raw formats
const polygon = {__type: 'Polygon', coordinates: coords};
const location = {__type: 'GeoPoint', latitude:10, longitude:10};
const polygon = { __type: 'Polygon', coordinates: coords };
const location = { __type: 'GeoPoint', latitude: 10, longitude: 10 };
const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI });
return reconfigureServer({
appId: 'test',
restAPIKey: 'rest',
publicServerURL: 'http://localhost:8378/1',
databaseAdapter
}).then(() => {
return databaseAdapter.createIndex('TestObject', {location: '2d'});
}).then(() => {
return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'});
}).then(() => {
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: {
'_method': 'POST',
location,
polygon,
polygon2: polygon
},
headers: defaultHeaders
});
}).then((resp) => {
return rp.post({
url: `http://localhost:8378/1/classes/TestObject/${resp.objectId}`,
json: {'_method': 'GET'},
headers: defaultHeaders
});
}).then((resp) => {
equal(resp.location, location);
equal(resp.polygon, polygon);
equal(resp.polygon2, polygon);
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
equal(indexes.length, 4);
equal(indexes[0].key, {_id: 1});
equal(indexes[1].key, {location: '2d'});
equal(indexes[2].key, {polygon: '2dsphere'});
equal(indexes[3].key, {polygon2: '2dsphere'});
done();
}, done.fail);
databaseAdapter,
})
.then(() => {
return databaseAdapter.createIndex('TestObject', { location: '2d' });
})
.then(() => {
return databaseAdapter.createIndex('TestObject', {
polygon: '2dsphere',
});
})
.then(() => {
return request({
method: 'POST',
url: 'http://localhost:8378/1/classes/TestObject',
body: {
_method: 'POST',
location,
polygon,
polygon2: polygon,
},
headers: defaultHeaders,
});
})
.then(resp => {
return request({
method: 'POST',
url: `http://localhost:8378/1/classes/TestObject/${
resp.data.objectId
}`,
body: { _method: 'GET' },
headers: defaultHeaders,
});
})
.then(resp => {
equal(resp.data.location, location);
equal(resp.data.polygon, polygon);
equal(resp.data.polygon2, polygon);
return databaseAdapter.getIndexes('TestObject');
})
.then(indexes => {
equal(indexes.length, 4);
equal(indexes[0].key, { _id: 1 });
equal(indexes[1].key, { location: '2d' });
equal(indexes[2].key, { polygon: '2dsphere' });
equal(indexes[3].key, { polygon2: '2dsphere' });
done();
}, done.fail);
});
it('polygon loop is not valid', (done) => {
const coords = [[0,0],[0,1],[1,0],[1,1]];
it('polygon coordinates reverse input', done => {
const Config = require('../lib/Config');
const config = Config.get('test');
// When stored the first point should be the last point
const input = [[12, 11], [14, 13], [16, 15], [18, 17]];
const output = [[[11, 12], [13, 14], [15, 16], [17, 18], [11, 12]]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(input));
obj
.save()
.then(() => {
return config.database.adapter._rawFind('TestObject', { _id: obj.id });
})
.then(results => {
expect(results.length).toBe(1);
expect(results[0].polygon.coordinates).toEqual(output);
done();
});
});
it('polygon loop is not valid', done => {
const coords = [[0, 0], [0, 1], [1, 0], [1, 1]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
obj.save().then(done.fail, done);
obj.save().then(done.fail, () => done());
});
});
+65 -35
View File
@@ -1,80 +1,105 @@
const ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
const ParsePubSub = require('../lib/LiveQuery/ParsePubSub').ParsePubSub;
describe('ParsePubSub', function() {
beforeEach(function(done) {
// Mock RedisPubSub
const mockRedisPubSub = {
createPublisher: jasmine.createSpy('createPublisherRedis'),
createSubscriber: jasmine.createSpy('createSubscriberRedis')
createSubscriber: jasmine.createSpy('createSubscriberRedis'),
};
jasmine.mockLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub', mockRedisPubSub);
jasmine.mockLibrary(
'../lib/Adapters/PubSub/RedisPubSub',
'RedisPubSub',
mockRedisPubSub
);
// Mock EventEmitterPubSub
const mockEventEmitterPubSub = {
createPublisher: jasmine.createSpy('createPublisherEventEmitter'),
createSubscriber: jasmine.createSpy('createSubscriberEventEmitter')
createSubscriber: jasmine.createSpy('createSubscriberEventEmitter'),
};
jasmine.mockLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub', mockEventEmitterPubSub);
jasmine.mockLibrary(
'../lib/Adapters/PubSub/EventEmitterPubSub',
'EventEmitterPubSub',
mockEventEmitterPubSub
);
done();
});
it('can create redis publisher', function() {
ParsePubSub.createPublisher({
redisURL: 'redisURL'
redisURL: 'redisURL',
redisOptions: { socket_keepalive: true },
});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith({redisURL: 'redisURL'});
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
.RedisPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith({
redisURL: 'redisURL',
redisOptions: { socket_keepalive: true },
});
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
});
it('can create event emitter publisher', function() {
ParsePubSub.createPublisher({});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
.RedisPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createPublisher).toHaveBeenCalled();
});
it('can create redis subscriber', function() {
ParsePubSub.createSubscriber({
redisURL: 'redisURL'
redisURL: 'redisURL',
redisOptions: { socket_keepalive: true },
});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith({redisURL: 'redisURL'});
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
.RedisPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith({
redisURL: 'redisURL',
redisOptions: { socket_keepalive: true },
});
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
});
it('can create event emitter subscriber', function() {
ParsePubSub.createSubscriber({});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
.RedisPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createSubscriber).toHaveBeenCalled();
});
it('can create publisher/sub with custom adapter', function() {
const adapter = {
const adapter = {
createPublisher: jasmine.createSpy('createPublisher'),
createSubscriber: jasmine.createSpy('createSubscriber')
}
createSubscriber: jasmine.createSpy('createSubscriber'),
};
ParsePubSub.createPublisher({
pubSubAdapter: adapter
pubSubAdapter: adapter,
});
expect(adapter.createPublisher).toHaveBeenCalled();
ParsePubSub.createSubscriber({
pubSubAdapter: adapter
pubSubAdapter: adapter,
});
expect(adapter.createSubscriber).toHaveBeenCalled();
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
.RedisPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
@@ -82,34 +107,39 @@ describe('ParsePubSub', function() {
});
it('can create publisher/sub with custom function adapter', function() {
const adapter = {
const adapter = {
createPublisher: jasmine.createSpy('createPublisher'),
createSubscriber: jasmine.createSpy('createSubscriber')
}
createSubscriber: jasmine.createSpy('createSubscriber'),
};
ParsePubSub.createPublisher({
pubSubAdapter: function() {
return adapter;
}
},
});
expect(adapter.createPublisher).toHaveBeenCalled();
ParsePubSub.createSubscriber({
pubSubAdapter: function() {
return adapter;
}
},
});
expect(adapter.createSubscriber).toHaveBeenCalled();
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
.RedisPubSub;
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
.EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
});
afterEach(function(){
jasmine.restoreLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
jasmine.restoreLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub');
afterEach(function() {
jasmine.restoreLibrary('../lib/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
jasmine.restoreLibrary(
'../lib/Adapters/PubSub/EventEmitterPubSub',
'EventEmitterPubSub'
);
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3628 -2116
View File
File diff suppressed because it is too large Load Diff
+669 -599
View File
File diff suppressed because it is too large Load Diff
+515 -425
View File
File diff suppressed because it is too large Load Diff
+61 -26
View File
@@ -1,43 +1,55 @@
'use strict';
/* Tests for ParseServer.js */
const express = require('express');
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
import PostgresStorageAdapter from '../src/Adapters/Storage/Postgres/PostgresStorageAdapter';
import ParseServer from '../src/ParseServer';
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
.default;
const PostgresStorageAdapter = require('../lib/Adapters/Storage/Postgres/PostgresStorageAdapter')
.default;
const ParseServer = require('../lib/ParseServer').default;
const path = require('path');
const { spawn } = require('child_process');
describe('Server Url Checks', () => {
const app = express();
app.get('/health', function(req, res){
res.json({
status: 'ok'
let server;
beforeAll(done => {
const app = express();
app.get('/health', function(req, res) {
res.json({
status: 'ok',
});
});
server = app.listen(13376, undefined, done);
});
app.listen(13376);
it('validate good server url', (done) => {
afterAll(done => {
server.close(done);
});
it('validate good server url', done => {
Parse.serverURL = 'http://localhost:13376';
ParseServer.verifyServerUrl(function(result) {
if(!result) {
if (!result) {
done.fail('Did not pass valid url');
}
done();
});
});
it('mark bad server url', (done) => {
it('mark bad server url', done => {
Parse.serverURL = 'notavalidurl';
ParseServer.verifyServerUrl(function(result) {
if(result) {
if (result) {
done.fail('Did not mark invalid url');
}
done();
});
});
it('handleShutdown, close connection', (done) => {
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
it('handleShutdown, close connection', done => {
const mongoURI =
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const postgresURI =
'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
let databaseAdapter;
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
databaseAdapter = new PostgresStorageAdapter({
@@ -50,17 +62,40 @@ describe('Server Url Checks', () => {
collectionPrefix: 'test_',
});
}
const newConfiguration = Object.assign({}, defaultConfiguration, { databaseAdapter });
const parseServer = ParseServer.start(newConfiguration, () => {
parseServer.handleShutdown();
parseServer.server.close((err) => {
if (err) {
done.fail('Close Server Error')
}
reconfigureServer({}).then(() => {
done();
const newConfiguration = Object.assign({}, defaultConfiguration, {
databaseAdapter,
serverStartComplete: () => {
parseServer.handleShutdown();
parseServer.server.close(err => {
if (err) {
done.fail('Close Server Error');
}
reconfigureServer({}).then(() => {
done();
});
});
});
},
});
const parseServer = ParseServer.start(newConfiguration);
});
it('does not have unhandled promise rejection in the case of load error', done => {
const parseServerProcess = spawn(
path.resolve(__dirname, './support/FailingServer.js')
);
let stdout;
let stderr;
parseServerProcess.stdout.on('data', data => {
stdout = data.toString();
});
parseServerProcess.stderr.on('data', data => {
stderr = data.toString();
});
parseServerProcess.on('close', code => {
expect(code).toEqual(1);
expect(stdout).toBeUndefined();
expect(stderr).toContain('MongoNetworkError');
done();
});
});
});
+184 -122
View File
@@ -1,155 +1,217 @@
const ParseServerRESTController = require('../src/ParseServerRESTController').ParseServerRESTController;
const ParseServer = require('../src/ParseServer').default;
const ParseServerRESTController = require('../lib/ParseServerRESTController')
.ParseServerRESTController;
const ParseServer = require('../lib/ParseServer').default;
const Parse = require('parse/node').Parse;
let RESTController;
describe('ParseServerRESTController', () => {
beforeEach(() => {
RESTController = ParseServerRESTController(Parse.applicationId, ParseServer.promiseRouter({appId: Parse.applicationId}));
})
it('should handle a get request', (done) => {
RESTController.request("GET", "/classes/MyObject").then((res) => {
expect(res.results.length).toBe(0);
done();
}, (err) => {
console.log(err);
jfail(err);
done();
});
RESTController = ParseServerRESTController(
Parse.applicationId,
ParseServer.promiseRouter({ appId: Parse.applicationId })
);
});
it('should handle a get request with full serverURL mount path', (done) => {
RESTController.request("GET", "/1/classes/MyObject").then((res) => {
expect(res.results.length).toBe(0);
done();
}, (err) => {
jfail(err);
done();
});
it('should handle a get request', done => {
RESTController.request('GET', '/classes/MyObject').then(
res => {
expect(res.results.length).toBe(0);
done();
},
err => {
console.log(err);
jfail(err);
done();
}
);
});
it('should handle a POST batch', (done) => {
RESTController.request("POST", "batch", {
it('should handle a get request with full serverURL mount path', done => {
RESTController.request('GET', '/1/classes/MyObject').then(
res => {
expect(res.results.length).toBe(0);
done();
},
err => {
jfail(err);
done();
}
);
});
it('should handle a POST batch', done => {
RESTController.request('POST', 'batch', {
requests: [
{
method: 'GET',
path: '/classes/MyObject'
path: '/classes/MyObject',
},
{
method: 'POST',
path: '/classes/MyObject',
body: {"key": "value"}
body: { key: 'value' },
},
{
method: 'GET',
path: '/classes/MyObject'
}
]
}).then((res) => {
expect(res.length).toBe(3);
done();
}, (err) => {
jfail(err);
done();
});
path: '/classes/MyObject',
},
],
}).then(
res => {
expect(res.length).toBe(3);
done();
},
err => {
jfail(err);
done();
}
);
});
it('should handle a POST request', (done) => {
RESTController.request("POST", "/classes/MyObject", {"key": "value"}).then(() => {
return RESTController.request("GET", "/classes/MyObject");
}).then((res) => {
expect(res.results.length).toBe(1);
expect(res.results[0].key).toEqual("value");
done();
}).fail((err) => {
console.log(err);
jfail(err);
done();
});
});
it('ensures sessionTokens are properly handled', (done) => {
let userId;
Parse.User.signUp('user', 'pass').then((user) => {
userId = user.id;
const sessionToken = user.getSessionToken();
return RESTController.request("GET", "/users/me", undefined, {sessionToken});
}).then((res) => {
// Result is in JSON format
expect(res.objectId).toEqual(userId);
done();
}).fail((err) => {
console.log(err);
jfail(err);
done();
});
});
it('ensures masterKey is properly handled', (done) => {
let userId;
Parse.User.signUp('user', 'pass').then((user) => {
userId = user.id;
return Parse.User.logOut().then(() => {
return RESTController.request("GET", "/classes/_User", undefined, {useMasterKey: true});
it('should handle a POST request', done => {
RESTController.request('POST', '/classes/MyObject', { key: 'value' })
.then(() => {
return RESTController.request('GET', '/classes/MyObject');
})
.then(res => {
expect(res.results.length).toBe(1);
expect(res.results[0].key).toEqual('value');
done();
})
.catch(err => {
console.log(err);
jfail(err);
done();
});
}).then((res) => {
expect(res.results.length).toBe(1);
expect(res.results[0].objectId).toEqual(userId);
done();
}, (err) => {
jfail(err);
done();
});
});
it('ensures no user is created when passing an empty username', (done) => {
RESTController.request("POST", "/classes/_User", {username: "", password: "world"}).then(() => {
jfail(new Error('Success callback should not be called when passing an empty username.'));
done();
}, (err) => {
expect(err.code).toBe(Parse.Error.USERNAME_MISSING);
expect(err.message).toBe('bad or missing username');
done();
});
it('ensures sessionTokens are properly handled', done => {
let userId;
Parse.User.signUp('user', 'pass')
.then(user => {
userId = user.id;
const sessionToken = user.getSessionToken();
return RESTController.request('GET', '/users/me', undefined, {
sessionToken,
});
})
.then(res => {
// Result is in JSON format
expect(res.objectId).toEqual(userId);
done();
})
.catch(err => {
console.log(err);
jfail(err);
done();
});
});
it('ensures no user is created when passing an empty password', (done) => {
RESTController.request("POST", "/classes/_User", {username: "hello", password: ""}).then(() => {
jfail(new Error('Success callback should not be called when passing an empty password.'));
done();
}, (err) => {
expect(err.code).toBe(Parse.Error.PASSWORD_MISSING);
expect(err.message).toBe('password is required');
done();
});
it('ensures masterKey is properly handled', done => {
let userId;
Parse.User.signUp('user', 'pass')
.then(user => {
userId = user.id;
return Parse.User.logOut().then(() => {
return RESTController.request('GET', '/classes/_User', undefined, {
useMasterKey: true,
});
});
})
.then(
res => {
expect(res.results.length).toBe(1);
expect(res.results[0].objectId).toEqual(userId);
done();
},
err => {
jfail(err);
done();
}
);
});
it('ensures no session token is created on creating users', (done) => {
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}).then((user) => {
expect(user.sessionToken).toBeUndefined();
const query = new Parse.Query('_Session');
return query.find({useMasterKey: true});
}).then(sessions => {
expect(sessions.length).toBe(0);
done();
}, done.fail);
it('ensures no user is created when passing an empty username', done => {
RESTController.request('POST', '/classes/_User', {
username: '',
password: 'world',
}).then(
() => {
jfail(
new Error(
'Success callback should not be called when passing an empty username.'
)
);
done();
},
err => {
expect(err.code).toBe(Parse.Error.USERNAME_MISSING);
expect(err.message).toBe('bad or missing username');
done();
}
);
});
it('ensures a session token is created when passing installationId != cloud', (done) => {
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}, {installationId: 'my-installation'}).then((user) => {
expect(user.sessionToken).not.toBeUndefined();
const query = new Parse.Query('_Session');
return query.find({useMasterKey: true});
}).then(sessions => {
expect(sessions.length).toBe(1);
expect(sessions[0].get('installationId')).toBe('my-installation');
done();
}, (err) => {
jfail(err);
done();
});
it('ensures no user is created when passing an empty password', done => {
RESTController.request('POST', '/classes/_User', {
username: 'hello',
password: '',
}).then(
() => {
jfail(
new Error(
'Success callback should not be called when passing an empty password.'
)
);
done();
},
err => {
expect(err.code).toBe(Parse.Error.PASSWORD_MISSING);
expect(err.message).toBe('password is required');
done();
}
);
});
it('ensures no session token is created on creating users', done => {
RESTController.request('POST', '/classes/_User', {
username: 'hello',
password: 'world',
})
.then(user => {
expect(user.sessionToken).toBeUndefined();
const query = new Parse.Query('_Session');
return query.find({ useMasterKey: true });
})
.then(sessions => {
expect(sessions.length).toBe(0);
done();
}, done.fail);
});
it('ensures a session token is created when passing installationId != cloud', done => {
RESTController.request(
'POST',
'/classes/_User',
{ username: 'hello', password: 'world' },
{ installationId: 'my-installation' }
)
.then(user => {
expect(user.sessionToken).not.toBeUndefined();
const query = new Parse.Query('_Session');
return query.find({ useMasterKey: true });
})
.then(
sessions => {
expect(sessions.length).toBe(1);
expect(sessions[0].get('installationId')).toBe('my-installation');
done();
},
err => {
jfail(err);
done();
}
);
});
});
+104 -92
View File
@@ -2,125 +2,137 @@
// Tests behavior of Parse Sessions
//
"use strict";
'use strict';
function setupTestUsers() {
const user1 = new Parse.User();
const user2 = new Parse.User();
const user3 = new Parse.User();
user1.set("username", "testuser_1");
user2.set("username", "testuser_2");
user3.set("username", "testuser_3");
user1.set('username', 'testuser_1');
user2.set('username', 'testuser_2');
user3.set('username', 'testuser_3');
user1.set("password", "password");
user2.set("password", "password");
user3.set("password", "password");
user1.set('password', 'password');
user2.set('password', 'password');
user3.set('password', 'password');
return user1.signUp().then(() => {
return user2.signUp();
}).then(() => {
return user3.signUp();
})
return user1
.signUp()
.then(() => {
return user2.signUp();
})
.then(() => {
return user3.signUp();
});
}
describe('Parse.Session', () => {
// multiple sessions with masterKey + sessionToken
it('should retain original sessionTokens with masterKey & sessionToken set', (done) => {
setupTestUsers().then((user) => {
const query = new Parse.Query(Parse.Session);
return query.find({
useMasterKey: true,
sessionToken: user.get('sessionToken')
});
}).then((results) => {
const foundKeys = [];
expect(results.length).toBe(3);
for(const key in results) {
const sessionToken = results[key].get('sessionToken');
if(foundKeys[sessionToken]) {
fail('Duplicate session token present in response');
break;
it('should retain original sessionTokens with masterKey & sessionToken set', done => {
setupTestUsers()
.then(user => {
const query = new Parse.Query(Parse.Session);
return query.find({
useMasterKey: true,
sessionToken: user.get('sessionToken'),
});
})
.then(results => {
const foundKeys = [];
expect(results.length).toBe(3);
for (const key in results) {
const sessionToken = results[key].get('sessionToken');
if (foundKeys[sessionToken]) {
fail('Duplicate session token present in response');
break;
}
foundKeys[sessionToken] = 1;
}
foundKeys[sessionToken] = 1;
}
done();
}).catch((err) => {
fail(err);
});
done();
})
.catch(err => {
fail(err);
});
});
// single session returned, with just one sessionToken
it('should retain original sessionTokens with just sessionToken set', (done) => {
it('should retain original sessionTokens with just sessionToken set', done => {
let knownSessionToken;
setupTestUsers().then((user) => {
knownSessionToken = user.get('sessionToken');
const query = new Parse.Query(Parse.Session);
return query.find({
sessionToken: knownSessionToken
setupTestUsers()
.then(user => {
knownSessionToken = user.get('sessionToken');
const query = new Parse.Query(Parse.Session);
return query.find({
sessionToken: knownSessionToken,
});
})
.then(results => {
expect(results.length).toBe(1);
const sessionToken = results[0].get('sessionToken');
expect(sessionToken).toBe(knownSessionToken);
done();
})
.catch(err => {
fail(err);
});
}).then((results) => {
expect(results.length).toBe(1);
const sessionToken = results[0].get('sessionToken');
expect(sessionToken).toBe(knownSessionToken);
done();
}).catch((err) => {
fail(err);
});
});
// multiple users with masterKey + sessionToken
it('token on users should retain original sessionTokens with masterKey & sessionToken set', (done) => {
setupTestUsers().then((user) => {
const query = new Parse.Query(Parse.User);
return query.find({
useMasterKey: true,
sessionToken: user.get('sessionToken')
});
}).then((results) => {
const foundKeys = [];
expect(results.length).toBe(3);
for(const key in results) {
const sessionToken = results[key].get('sessionToken');
if(foundKeys[sessionToken] && sessionToken !== undefined) {
fail('Duplicate session token present in response');
break;
it('token on users should retain original sessionTokens with masterKey & sessionToken set', done => {
setupTestUsers()
.then(user => {
const query = new Parse.Query(Parse.User);
return query.find({
useMasterKey: true,
sessionToken: user.get('sessionToken'),
});
})
.then(results => {
const foundKeys = [];
expect(results.length).toBe(3);
for (const key in results) {
const sessionToken = results[key].get('sessionToken');
if (foundKeys[sessionToken] && sessionToken !== undefined) {
fail('Duplicate session token present in response');
break;
}
foundKeys[sessionToken] = 1;
}
foundKeys[sessionToken] = 1;
}
done();
}).catch((err) => {
fail(err);
});
done();
})
.catch(err => {
fail(err);
});
});
// multiple users with just sessionToken
it('token on users should retain original sessionTokens with just sessionToken set', (done) => {
it('token on users should retain original sessionTokens with just sessionToken set', done => {
let knownSessionToken;
setupTestUsers().then((user) => {
knownSessionToken = user.get('sessionToken');
const query = new Parse.Query(Parse.User);
return query.find({
sessionToken: knownSessionToken
});
}).then((results) => {
const foundKeys = [];
expect(results.length).toBe(3);
for(const key in results) {
const sessionToken = results[key].get('sessionToken');
if(foundKeys[sessionToken] && sessionToken !== undefined) {
fail('Duplicate session token present in response');
break;
setupTestUsers()
.then(user => {
knownSessionToken = user.get('sessionToken');
const query = new Parse.Query(Parse.User);
return query.find({
sessionToken: knownSessionToken,
});
})
.then(results => {
const foundKeys = [];
expect(results.length).toBe(3);
for (const key in results) {
const sessionToken = results[key].get('sessionToken');
if (foundKeys[sessionToken] && sessionToken !== undefined) {
fail('Duplicate session token present in response');
break;
}
foundKeys[sessionToken] = 1;
}
foundKeys[sessionToken] = 1;
}
done();
}).catch((err) => {
fail(err);
});
done();
})
.catch(err => {
fail(err);
});
});
});
+3158 -2770
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,7 +1,7 @@
const ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer')
.ParseWebSocket;
describe('ParseWebSocket', function() {
it('can be initialized', function() {
const ws = {};
const parseWebSocket = new ParseWebSocket(ws);
@@ -11,7 +11,7 @@ describe('ParseWebSocket', function() {
it('can handle events defined in typeMap', function() {
const ws = {
on: jasmine.createSpy('on')
on: jasmine.createSpy('on'),
};
const callback = {};
const parseWebSocket = new ParseWebSocket(ws);
@@ -22,7 +22,7 @@ describe('ParseWebSocket', function() {
it('can handle events which are not defined in typeMap', function() {
const ws = {
on: jasmine.createSpy('on')
on: jasmine.createSpy('on'),
};
const callback = {};
const parseWebSocket = new ParseWebSocket(ws);
@@ -33,10 +33,10 @@ describe('ParseWebSocket', function() {
it('can send a message', function() {
const ws = {
send: jasmine.createSpy('send')
send: jasmine.createSpy('send'),
};
const parseWebSocket = new ParseWebSocket(ws);
parseWebSocket.send('message')
parseWebSocket.send('message');
expect(parseWebSocket.ws.send).toHaveBeenCalledWith('message');
});
+10 -6
View File
@@ -1,7 +1,7 @@
const ParseWebSocketServer = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocketServer;
const ParseWebSocketServer = require('../lib/LiveQuery/ParseWebSocketServer')
.ParseWebSocketServer;
describe('ParseWebSocketServer', function() {
beforeEach(function(done) {
// Mock ws server
const EventEmitter = require('events');
@@ -16,11 +16,15 @@ describe('ParseWebSocketServer', function() {
const onConnectCallback = jasmine.createSpy('onConnectCallback');
const http = require('http');
const server = http.createServer();
const parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
const parseWebSocketServer = new ParseWebSocketServer(
server,
onConnectCallback,
5
).server;
const ws = {
readyState: 0,
OPEN: 0,
ping: jasmine.createSpy('ping')
ping: jasmine.createSpy('ping'),
};
parseWebSocketServer.emit('connection', ws);
@@ -31,10 +35,10 @@ describe('ParseWebSocketServer', function() {
expect(ws.ping).toHaveBeenCalled();
server.close();
done();
}, 10)
}, 10);
});
afterEach(function(){
afterEach(function() {
jasmine.restoreLibrary('ws', 'Server');
});
});

Some files were not shown because too many files have changed in this diff Show More