Compare commits

..
399 Commits
Author SHA1 Message Date
Florent Vilmart d8fd670013 ⚡ Release 2.6.2 (#4183) 2017-09-18 17:10:08 -04:00
marvelm 07ae85eef0 Prefix default push channel with applicationId (#4182) 2017-09-18 15:25:03 -04:00
Florent Vilmart a5ce9fc175 Refactor pushStatusHandler to use Parse instead of direct access (#4173)
* Refactors pushStatusHandler to use HTTP interface so we can bind CloudCode hooks

* Handle correctly nested dot atomic operations

* Better handling of restricted class names, add support for afterSave _PushStatus

* Adds simple testing for afterSave(PushStatus)

* Reverts jobStatusHandler

* Addresses fixes

* adds delays to all methods
2017-09-18 15:01:07 -04:00
Florent Vilmart a39d045c7d Fixes issue #4150: Session management (#4152)
* Adds tests

* Provide fix

* Fix post sessions (#4167)

* add tests

* provide fix

* remove console.log
2017-09-18 14:53:11 -04:00
Florent Vilmart ba0a51ddc9 Fixes unhandled promise rejection error while sending push (#4172) 2017-09-17 09:57:07 -04:00
Florent Vilmart 8d8a8b250e Fixes issue affecting liveQuery on location null/undefined values (#4171) 2017-09-17 11:57:11 +03:00
Florent Vilmart d0184f438d Live Query basic monitoring (#4168)
* Adds uuid based client identification to prevent overflows

* no-super

* Adds cloud code monitoring

* fixes test

* nit
2017-09-15 17:20:51 -04:00
greenkeeper[bot] 7ecb36e71e chore(package): update nodemon to version 1.12.1 (#4166) 2017-09-15 09:23:48 -04:00
greenkeeper[bot] be9f27493d fix(package): update ws to version 3.2.0 (#4165) 2017-09-15 08:10:18 -04:00
greenkeeper[bot] 64df33157d chore(package): update mongodb-runner to version 3.6.0 (#4164) 2017-09-15 08:09:35 -04:00
Florent Vilmart d598d73f36 Adds ability to track sent/failed PerUTCOffset in the PushWorker (#4158)
* Adds ability to track sent/failed PerUTCOffset in the PushWorker

- for scheduled push notifications at a certain time, it helps keep track of the state

* Makes sure we track it all correctly

* Adds to Postgres
2017-09-13 17:28:20 -04:00
greenkeeper[bot] be2760f9dd fix(package): update pg-promise to version 6.5.3 (#4154) 2017-09-13 09:32:26 -04:00
greenkeeper[bot] c121b9787e fix(package): update winston-daily-rotate-file to version 1.5.0 (#4151) 2017-09-12 18:26:08 -04:00
Florent Vilmart 3a17904ce8 Push: Cleanup invalid device tokens (#4149)
* Adds collecting invalid / expired device tokens from GCM / APNS

* Cleanup invalid installations based on responses from the adapters

* Adds test for cleanup

* Adds tests for removal
2017-09-12 14:53:05 -04:00
greenkeeper[bot] a1554d04ab fix(package): update body-parser to version 1.18.1 (#4148) 2017-09-12 14:19:03 -04:00
greenkeeper[bot] 956f1df0f0 chore(package): update babel-eslint to version 8.0.0 (#4147) 2017-09-12 14:18:26 -04:00
Anthony Mosca 4dce3bd63c Add support for more audience fields. (#4145)
* Add support for more audience fields.

* Only update audience when defined audience_id.
2017-09-11 22:06:21 -04:00
Florent Vilmart 9fbb5e29e8 ⚡ Release 2.6.1 (#4146)
* ⚡ Release 2.6.1

* Update CHANGELOG.md
2017-09-11 11:23:07 -04:00
Florent Vilmart a660a0c25f fix: Issue #4142 (#4144)
* Tweaks test in order to show the error

- Session is effectively created when it should not

* Do not create a session when users need verified accounts on signup
2017-09-11 11:07:39 -04:00
Florent Vilmart 839a117246 Fixes #2885 duplicate sessions (#4143)
* Adds test to repro the issue

* Improved test

* Destroy duplicate sessions for User/Installation-id pair

- Sessions will also be created with action login instead of signup when using 3rd party auth
2017-09-11 09:52:18 -04:00
marvelm bc3cef2cd9 Support local time for scheduled pushes (#4137)
* Handle local push time

* PR feedback

* Improve timezone detection with regex

* Use indexOf instead of endsWith

* Add documentation

* Add end to end test for scheduled pushes in local time

* Revert changes to npm-git script

* clean up
2017-09-11 09:31:46 -04:00
greenkeeper[bot] 21f4411571 fix(package): update pg-promise to version 6.3.2 (#4009) 2017-09-09 14:02:54 -04:00
greenkeeper[bot] 0fb16a7848 chore(package): update babel-cli to version 6.26.0 (#4098) 2017-09-09 14:02:36 -04:00
Florent Vilmart 0f840b6bb8 Fix/issue pointer permissions (#4141)
* Makes sure we don't override roles

* Reduces the query size whith pointer permissions

- Does not return as $and if not needed
- Returns just the query with the additional constraint

* Do not use $in if include is just of length 1
2017-09-09 14:02:07 -04:00
Antonio Davi Macedo Coelho de Castro 019f9e55e1 Fix(MongoTransform): Ignore Audience legacy fields (#4018) 2017-09-09 13:27:54 -04:00
Jeremy May 121d151af9 Add master key override to live query ACL checks (#4133)
* Add master key override to live query ACL checks

* Fix mockClient so masterKey tests work correctly
2017-09-09 13:26:18 -04:00
Worathiti Manosroi 52c4dd3704 Refactor masterKeyIps and update readme (#4050)
* update choose_password to have the confirmation

* just a revert to the old one

* refactor and update readme

* just revert some spaces
2017-09-09 13:24:43 -04:00
Stephen Tuso 952e64d94a Update FilesAdapter comments (#4065) 2017-09-09 13:11:16 -04:00
greenkeeper[bot] 1914083942 fix(package): update body-parser to version 1.18.0 (#4139) 2017-09-09 13:09:46 -04:00
Florent Vilmart 139b9e1cb3 Finding areas that are untested and need love (#4131)
* Makes InstallationRouter like others

* Adds testing for Range file requests

- Fixes issue with small requests (0-2)

* Revert "Makes InstallationRouter like others"

This reverts commit e2d2a16ebf2757db6138c7b5b33c97c56c69ead6.

* Better handling of errors in FilesRouter

* Fix incorrectness in range requests

* Better/simpler logic

* Only on mongo at it requires Gridstore

* Open file streaming to all adapters supporting it

* Improves coverage of parsers

* Ensures depreciation warning is effective

* Removes unused function

* de-duplicate logic

* Removes necessity of overriding req.params.className on subclasses routers

* Use babel-preset-env to ensure min-version compatible code

* removes dead code

* Leverage indexes in order to infer which field is duplicated upon signup

- A note mentioned that it would be possible to leverage using the indexes on username/email to infer which is duplicated

* Small nit

* Better template to match column name

* Restores original implementation for safety

* nits
2017-09-05 17:51:11 -04:00
Florent Vilmart 3079270b3e Optimizations (#4135)
* removes costly json serialization to InMemoryCacheAdapter

* Always cache a copy of the array

* Use own mapValues

* Makes sure we dont make unnecessary calls to the logger

* Do not bypass loggers with silent logging (only applies to stdout)

* warn is not warning

* use ===

* Wrap logRequest / logResponse in the loggerController for more granular control

Also give the ability to pass functions to the logger so we don't serialize too early in JSON (costly)

* reconfiguring winston would override the transports levels and make subsequent tests fail
2017-09-04 20:47:49 -04:00
greenkeeper[bot] 17f4dcd176 chore(package): update nodemon to version 1.11.1 (#4134) 2017-09-04 15:17:33 -07:00
Diamond Lewis 5aafc93476 Text Index Support (#4081)
* add text index support

* additional validation

* multiple text index error

* rename function

* Delete text indexes message
2017-09-02 12:31:19 -04:00
Florent Vilmart 0bace67bb1 Adds push localization feature flag on serverInfo (#4130) 2017-09-01 18:08:02 -04:00
Florent Vilmart 6df944704c Adds support for localized push notification in push payload (#4129)
* Adds support for localized push data keys

- passign alert-[lang|locale] or title-[lang|locale] will inject the
  proper locale on the push body based on the installation

* Better handling of the default cases

* Updates changelog

* nits

* nits
2017-09-01 15:22:02 -04:00
Florent Vilmart 540daa4c4a ⚡ Release 2.6.0 (#4126) 2017-08-30 14:08:20 -04:00
Florent Vilmart 2e1ba66fd2 Fix/user update issue (#4123)
* Adds failing test,

the _User object is not updated as soon as you pass some authData part of the PUT

* Do not run the DB call when updating the user with new auth data, just part of the rest
2017-08-29 16:34:59 -04:00
Arthur Cinader a103871cbc fix some typos. (#4112)
* fix some typos.

* more typos....
2017-08-29 14:20:51 -04:00
Arthur Cinader f8403943ab Greenkeeper/parse server s3 adapter 1.2.0 (#4118)
* fix(package): update parse-server-s3-adapter to version 1.2.0

* Add changelog note to explain the adapter change.
2017-08-29 14:20:25 -04:00
greenkeeper[bot] da2bfca618 fix(package): update ws to version 3.1.0 (#4053) 2017-08-29 10:08:25 -07:00
greenkeeper[bot] 1798c07874 fix(package): update mime to version 1.4.0 (#4120) 2017-08-29 09:46:10 -07:00
greenkeeper[bot] e2d1fd3fe0 fix(package): update semver to version 5.4.0 (#4044) 2017-08-29 09:14:54 -07:00
Florent Vilmart ea67d23ef4 Improvements for sending push performance (#4122)
* Adds test for stalled pushStatus when audience is empty

* fixup! Adds test for stalled pushStatus when audience is empty

* Do not enqueue when count is 0, enforce deviceToken exists, stop badge ordering
2017-08-29 11:47:01 -04:00
greenkeeper[bot] cfd7bc0f3b chore(package): update uws to version 8.14.1 (#4078) 2017-08-28 10:17:13 -07:00
greenkeeper[bot] c4d921e3eb chore(package): update jasmine to version 2.8.0 (#4115) 2017-08-24 12:49:46 -07:00
greenkeeper[bot] 8513b697e9 chore(package): update bcrypt to version 1.0.3 (#4113) 2017-08-24 10:15:23 -04:00
Arthur Cinader ff4ae55e34 Fix minor comment typo (#4110)
* Fix minor comment typo

* get the rest of the typos.  use US spelling for behavior.
2017-08-23 10:41:27 -07:00
greenkeeper[bot] 5d359c3b63 chore(package): update babel-register to version 6.26.0 (#4097) 2017-08-23 11:35:52 -04:00
greenkeeper[bot] 6e23822f99 chore(package): update babel-core to version 6.26.0 (#4096) 2017-08-23 11:35:40 -04:00
greenkeeper[bot] b45f308ff9 fix(package): update mongodb to version 2.2.31 (#4089) 2017-08-23 11:35:23 -04:00
greenkeeper[bot] 3ed6dd2685 fix(package): update redis to version 2.8.0 (#4087) 2017-08-23 11:35:08 -04:00
Vitaly Tomilov 2b4c7570ea Update PostgresStorageAdapter.js (#4094)
* proper use of the connections
* proper integrity: creating tables within a single transaction.
2017-08-23 11:34:57 -04:00
Diamond Lewis c6546218f4 PG: Support for nested contains and containedIn (#4109) 2017-08-23 11:33:57 -04:00
Florent Vilmart c2c059f625 Smaller build matrix (#3986) 2017-08-11 13:17:58 -04:00
Arthur Cinader 008cb1dbfe Fix nit whitespace issue that is breaking lint. (#4092)
* Not sure how this got introduced without lint catching either in the upgrade of a lib or a pr on these files, but innocuous enough that i don't think we need to investigate further?

* Update eslint to make all happy!
2017-08-10 23:31:32 +03:00
greenkeeper[bot] 2cec92007a chore(package): update jasmine to version 2.7.0 (#4061) 2017-08-10 11:18:36 -04:00
greenkeeper[bot] 47f00e606f chore(package): update cross-env to version 5.0.2 (#4071) 2017-08-10 08:42:51 -04:00
Florent Vilmart 92d51def6b Fixes issue affecting linking users to a 3rd party auth (#4047)
* Fixes issue affecting linking users to a 3rd party auth

* Fixes problematic test

* Better fix

* nits
2017-07-25 15:26:34 -04:00
Worathiti Manosroi 7e54265f6d Security: limit Masterkey remote access (#4017)
* update choose_password to have the confirmation

* add comment mark

* First version, no test

* throw error right away instead of just use masterKey false

* fix the logic

* move it up before the masterKey check

* adding some test

* typo

* remove the choose_password

* newline

* add cli options

* remove trailing space

* handle  in case the server is behind proxy

* add getting the first ip in the ip list of xff

* sanity check the ip in config if it is a valid ip address

* split ip extraction to another function

* trailing spaces
2017-07-23 12:26:30 -04:00
Diamond Lewis 811d8b0c7a pg support for null in containedAll (#4026) 2017-07-23 12:11:02 -04:00
Miguel Serrano 3c79cae1b2 feat: add request headers to trigger functions (#4012)
* add request headers to trigger functions

* reverse changes

* add headers in request config and trigger request
2017-07-14 13:19:00 -04:00
Diamond Lewis e6cc8204b3 Add Polygon Type To Schema / PolygonContain to Query (#3944)
* Added type polygon to schema

* refactoring and more tests

* fix tests

* update test and transform

* add support for polygonContains

* fix transform.mongoObjectToParseObject test

* add indexes for polygon

* index test

* postgres test fix

* remove invalid loop test

* add invalid loop test

* nit
2017-07-11 23:33:45 -04:00
greenkeeper[bot] 0571c6f95e fix(package): update mongodb to version 2.2.30 (#4003) 2017-07-08 14:18:24 -04:00
greenkeeper[bot] 58079f00f9 fix(package): update pg-promise to version 6.3.1 (#3992) 2017-07-08 14:18:10 -04:00
RiverAge a26e4c7470 fix: proper error message when login with wechat fails (#4000)
* fix typo

it should be weixin not qq.

* change error message from weixin to wechat

* remove unnecessary check
2017-07-08 08:29:02 -04:00
greenkeeper[bot] 9d6b4f4cc4 fix(package): update commander to version 2.11.0 (#3988) 2017-07-03 21:27:21 -04:00
Florent Vilmart d158023083 ⚡ Release 2.5.3 (#3989) 2017-07-03 17:50:04 +03:00
Florent Vilmart ad52ed64af Lets increment/set badges on all _Installations (#3970)
* Lets increment/set badges on all _Installations

* Makes sure PG update where query is properly formed with empty query
2017-07-03 10:16:04 -04:00
Florent Vilmart 5931aa8851 ⚡ Release 2.5.2 (#3985)
* Adds ability to configure cache from cli

* dont use array.includes for node 4.6

* Changelog and version bump

* Removes runtime check for version

* Build releases on 4.6
2017-07-02 20:50:13 -04:00
rihadavid 287810443b Include username in password reset email (#3969)
When user resets his password, he does it by filling his email. But often he could also not remember his username, which he needs for logging in. I think it's better to include the username in the password reset email, what do you think?
2017-07-02 16:20:25 -04:00
Florent Vilmart 457e21d80f ⚡ Release 2.5.1 (#3984)
* Update CHANGELOG.md

* Update package.json
2017-07-02 16:02:55 -04:00
Florent Vilmart f42ccba5b1 fix(package): update pg-promise to version 6.3.0 (#3982)
* fix(package): update pg-promise to version 6.3.0

Closes #3954

* Restores PG adapter after tests that close the connection

* investigation

* Use isolated parse-server to not impeed on other specs

* Update PostgresInitOptions.spec.js
2017-07-02 14:29:54 -04:00
greenkeeper[bot] d8a257d951 Update parse-server-push-adapter to the latest version 🚀 (#3983)
* fix(package): update parse-server-push-adapter to version 2.0.0

* Update helper.js

* default push config to android as ios is pickier
2017-07-01 23:34:47 -04:00
greenkeeper[bot] 2f00e4afda chore(package): update parse-server-s3-adapter to version 1.1.0 (#3981) 2017-07-01 19:26:21 -04:00
Florent Vilmart 95430bb4e8 Use LRU cache as default mechanism for caching in memory (#3979)
* Use LRU cache as default mechanism for caching in memory

* Return null as what’s expected
2017-06-30 16:54:35 -04:00
greenkeeper[bot] 29c2fad671 fix(package): update parse to version 1.10.0 (#3958) 2017-06-27 14:02:01 -04:00
Benjamin Wilson Friedman 622f97064a Added minimum node version requirement (#3962) 2017-06-27 14:01:49 -04:00
Steven Shipton 51d2dd92cb Add config for objectId size (#3950)
* Add objectId config property, default to 10

* Update Config constructor

* Add test for backwards compatibility when changing objectId size
2017-06-27 13:22:43 +03:00
Florent Vilmart bd6816c14c ⚡ Release 2.5.0 (#3957)
* Update package.json

* Update CHANGELOG.md
2017-06-26 10:40:20 -04:00
Denis Trofimov 91193c77eb fix(vkontakte auth adapter): replace deadlocked promise with a live one (#3956) 2017-06-26 08:33:08 -04:00
Tyler Brock fedb381925 Remove warning for invalid MongoDB driver option (#3952) 2017-06-25 13:00:54 -04:00
greenkeeper[bot] d094a70a3b fix(package): update pg-promise to version 5.9.7 (#3953) 2017-06-25 12:58:04 -04:00
Florent Vilmart 31e2288c3d fix(package): update pg-promise to version 5.9.6 (#3949)
Closes #3943
2017-06-25 13:18:35 +03:00
greenkeeper[bot] a48044c244 fix(package): update commander to version 2.10.0 (#3948) 2017-06-23 12:34:55 -04:00
Antonio Davi Macedo Coelho de Castro b6298feaa7 Read preference option per query (#3865) 2017-06-21 23:18:10 +03:00
Antonio Davi Macedo Coelho de Castro 422723fa31 Fix beforeQuery trigger when getting objects through GET API (#3862) 2017-06-21 08:24:51 -04:00
Antonio Davi Macedo Coelho de Castro a0d1a3517f fix(DatabaseController): Do not match any entry when searching for null in relation field (#3924) 2017-06-21 08:23:20 -04:00
Antonio Davi Macedo Coelho de Castro 4509d25471 Endpoints for audiences CRUD (#3861) 2017-06-21 08:54:13 +03:00
greenkeeper[bot] e94991b368 Update dependencies to enable Greenkeeper 🌴 (#3940)
* chore(package): update dependencies

* docs(readme): add Greenkeeper badge

* Fix indent issues with eslint 4.0

see http://eslint.org/docs/user-guide/migrating-to-4.0.0\#-the-indent-rule-is-more-strict
2017-06-20 09:15:26 -07:00
Florent Vilmart 16954c2f74 fix(#3898): session token deletion (#3937)
* fix(#3898): session token deletion

* nits
2017-06-16 12:56:28 -04:00
Florent Vilmart 9d79ba1ccb Server info proper values (#3933)
* Adds failing test

* Makes sure booleans are proper booleans

* nits

* Double negate so it also takes care of null
2017-06-15 15:09:33 -04:00
IlyaDiallo 92fa6f2b8a The 'beforeSave' trigger breaks the dot notation for subdocuments (cf #567) (#3912)
* The 'beforeSave' trigger breaks requests using the dot notation for subdocuments (cf #567)

* Convert 'var' to 'let' / 'const'

* Convert 'var' to 'const'
2017-06-14 21:51:41 +03:00
Florent Vilmart f0949a1310 feat: Job Scheduling (#3927)
* Adds back _JobSchedule as volatile class

* wip

* Restores jobs endpoints for creation, update and deletion

* Adds tests

* Fixes postgres tests

* Enforce jobName exists before creating a schedule
2017-06-14 13:07:00 -04:00
Florent Vilmart 9256b2d7e6 chore: fix flaky test (#3928)
* logOut is async so handle with a promise
2017-06-13 23:54:15 -04:00
Diamond Lewis 8b21d5ab80 feat: Full Text Search Support (#3904)
* Full Text Support

* invalid input test

* Support for sort

* index exist test

* clean up

* better error messaging

* postgres support

* error instructions for $diacritic and $case sensitivity

* nit

* nit

* nit

* separate test for full text
2017-06-13 21:42:59 -04:00
Arthur Cinader 5f991e90fb chore: put loose test into a describe block where it belongs... (#3920) 2017-06-13 10:40:44 -04:00
Antonio Davi Macedo Coelho de Castro 4aa4ebe807 chore: adds test for multiple dont select query (#3799) (#3885) 2017-06-13 10:40:17 -04:00
greenkeeper[bot] 58ca561ac4 chore(package): update babel-core to version 6.25.0 (#3917) 2017-06-13 10:38:47 -04:00
greenkeeper[bot] 3c845878ac chore(package): update cross-env to version 5.0.1 (#3914) 2017-06-13 10:38:29 -04:00
greenkeeper[bot] 2f1b28c456 fix(package): update pg-promise to version 5.9.1 (#3922) 2017-06-13 10:38:12 -04:00
greenkeeper[bot] e3d3daa142 fix(package): update lru-cache to version 4.1.0 (#3910) 2017-06-13 10:37:48 -04:00
Worathiti Manosroi 7d70af60ba fix: volatileClasses are masterKey only (#3916)
* volatileClass should respect the schema if defined

* masterKey only for the volatileClasses

* add some test …
2017-06-13 10:36:40 -04:00
greenkeeper[bot] c4fbc1f358 fix(package): update pg-promise to version 5.9.0 (#3908) 2017-06-04 21:56:29 -04:00
greenkeeper[bot] b98fbe495b fix(package): update mongodb to version 2.2.28 (#3899) 2017-06-04 10:25:25 +03:00
greenkeeper[bot] 8cb9706eaa fix(package): update pg-promise to version 5.8.2 (#3902) 2017-06-04 09:41:43 +03:00
Ruben ee23fc3983 Adding logLevel info to README (#3901)
* Adding logLevel info to README

* More detailed description as recommended

* Inline link added for winston

* small text change
2017-06-04 08:54:39 +03:00
greenkeeper[bot] 9e2afca092 chore(package): update nyc to version 11.0.2 (#3903) 2017-06-02 21:01:21 -04:00
Diamond Lewis 4b16a72992 feat(postgres): Add Bytes to Support (#3894)
* Add bytes to schema

* postgres support

* test cases

* bytes work parse object
2017-05-31 22:23:49 -04:00
Diamond Lewis e8be98ded2 chore(Query): $withinPolygon: 3 point minimum and boundary testing. (#3889)
* added 3 point minimum constraint to within-polygon

* test nit

* test for open and closed paths
2017-05-31 18:08:37 -04:00
greenkeeper[bot] 9ad8697c7a fix(package): update pg-promise to version 5.8.1 (#3883) 2017-05-30 10:00:28 +03:00
Antonio Davi Macedo Coelho de Castro 6cc99aa193 fix(Restwrite): Do not send verification email if users is authenticated using some auth provider (#2660) (#3882) 2017-05-29 20:06:40 -04:00
Florent Vilmart 31744c59ef ⚡ Release 2.4.2 (#3878)
* ⚡ Release 2.4.2

* reflects latest pg-promise version
2017-05-29 11:03:51 +03:00
Florent Vilmart 9a6fb82b68 chore(package): update pg-promise to version 5.7.2 (#3879) 2017-05-28 22:35:02 -04:00
Diamond Lewis b692e8578d fix(Postgres): Support for GeoPoint equality query (#3875)
* Postgres doesn't support this query type yet

* removing conflict

* near test

* remove trailing space
2017-05-28 20:42:51 -04:00
Florent Vilmart bd2ea87c1d docs: Improves explanations on Postgres testing (#3873)
* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2017-05-28 20:36:35 -04:00
Florent Vilmart c2abbae92d fix(Users): Makes sure verifying emails triggers hooks and liveQuery (#3851)
* Use RestWrite when verifying emails so hooks are called (as master)

* Fixes tests for postgres

* nit

* Makes rest.update support a full where instead of objectId

* Use rest.update to guaranteed proper beforeSave and liveQuery calls
2017-05-28 20:34:49 -04:00
Florent Vilmart 73aafa2d24 fix(tests): Stabilize flaky tests (#3876)
* Longer TTL, shorter wait

* Makes sure we wait for the logout

- Parse.User.logOut is async and was making a test flaky
2017-05-28 20:01:33 -04:00
Florent Vilmart 57efd89b3d fix(AuthAdapters): Do not revalidate auth data if hasn't changed (#3867) (#3872)
* Adds test for #3867

* Always Skip authData validation when nothing is mutated
2017-05-28 17:50:16 -04:00
Florent Vilmart 38a525ba5f chore(testing): only use TESTING=1 as the testing flag (#3877) 2017-05-29 00:04:25 +03:00
Diamond Lewis c99fdea6fb feat(ParseQuery): Added 'withinPolygon' support for GeoPoints (#3866)
* Added 'withinPolygon' to query

* Unit test for withinPolygon

* More Unit Test

* withinPolygon fix for Postgres

* Fix nit

nit?
2017-05-28 12:42:16 -04:00
Diamond Lewis a380fcf2c7 fix(postgres): Geopoint issue #3285 (#3874) 2017-05-28 12:41:09 -04:00
Vitaly Tomilov d149d16fce fix(PostgresStorageAdapter): Use transactions when deleting classes (#3869)
* Update PostgresStorageAdapter.js

refactoring `deleteClass`.

* Update PostgresStorageAdapter.js
2017-05-28 10:48:32 -04:00
Serge Bornow aedaae1f23 chore(dependencies): Bump to Mongodb client lib (#3868) 2017-05-27 15:52:36 -04:00
Vitaly Tomilov 14e2d26d12 chore(PostgresStorageAdapter): nits (#3870)
refactoring method + removing a comment that's no longer needed.
2017-05-27 15:51:09 -04:00
Anthony Mosca 1f11ad5d74 chore(PushController): Fix push controller tests. (#3853) 2017-05-23 20:22:35 -04:00
Florent Vilmart 67a1da8f9f ⚡ Release 2.4.1 (#3845) 2017-05-22 13:21:16 -04:00
Florent Vilmart b5a2042d12 Fixes issue #3835 affecting relation updates (#3836)
* Adds test for 3835

* Makes sure we run relational updates AFTER validating access to the object

* Always run relation udpates last
2017-05-22 12:34:00 -04:00
Felipe Andrade 03b6449fe1 [PushController] Fixes issue with undefined push_time (#3717) (#3824)
* Fixes #3717

This fixes PR #3717. Sending push with parse-server@2.4.0 returns error
504 GATEWAY_TIMEOUT. This happens when push_time is not set (default).

* Fix lint issues

* Fix in PushController and add tests

Add a test to check push_time format and if it should schedule push
when the parse-server is configured
2017-05-20 12:07:45 -04:00
greenkeeper[bot] 35d781a160 fix(package): update body-parser to version 1.17.2 (#3827) 2017-05-20 12:05:38 -04:00
greenkeeper[bot] ad71dc40ca chore(package): update pg-promise to version 5.7.1 (#3826) 2017-05-20 12:05:25 -04:00
greenkeeper[bot] 36607b244e chore(package): update ws to version 3.0.0 (#3825) 2017-05-20 12:05:09 -04:00
Florent Vilmart 17a2d269ef Always clear sessions when user password is updated (#3821)
* Adds repro to  issue #3289

* Always clear sessions when password is updated
2017-05-16 11:13:09 -07:00
Florent Vilmart 9dbb89a2e4 Fixes postgres flaky test (#3822)
* Changes expected error code

* nits
2017-05-16 09:06:17 -07:00
Florent Vilmart ab04641ed0 Create CODE_OF_CONDUCT.md (#3812) 2017-05-15 10:54:21 -04:00
Florent Vilmart 47e2db6c47 ⚡ Release 2.4.0 (#3816) 2017-05-15 10:47:10 -04:00
greenkeeper[bot] 4e9e33cfbc chore(package): update pg-promise to version 5.7.0 (#3815) 2017-05-14 22:55:33 -04:00
Florent Vilmart 691cf5988a Adds count class level permission (#3814)
* Adds count class level permission

* fixup! Adds count class level permission

* Adds missing count property on beforeFind request object

* nits
2017-05-14 21:47:30 -04:00
Florent Vilmart d71683a7e2 Code maintenance, small refactors (#3811)
* Removes promise wrapping in AccountLockoutPolicy

* Use bcrypt promises as globally available
2017-05-14 19:37:42 -04:00
Florent Vilmart ab5b759e26 [CI] test with 6.10 and 7.10, mongoDB 3.2 and 3.4 (#3787)
* Node modernization on CI

* Makes sure tests dont yeild unhandled promise rejections

* Adds small delay to startDB

* Adds mongodb service

* testing default

* stupid

* testing with silent nugget

* proper versions

* Single release step
2017-05-14 10:50:53 -04:00
Florent Vilmart f40019a326 build docker images on docker hub 2017-05-12 23:13:50 -04:00
Florent Vilmart 391ac97494 [ops] use build stages in travis (#3810) 2017-05-12 22:55:15 -04:00
Florent Vilmart 88de01f3d8 Fix issue _PushStatus stuck sending (#3808)
* Adds test for not set device tokens

* Properly filter the installations without a deviceToken

* nit for slower PG test

* nit
2017-05-12 14:24:37 -04:00
Florent Vilmart 4a724e8ab1 chore(package): update uws to version 0.14.5 (#3809)
https://greenkeeper.io/
2017-05-12 14:07:18 -04:00
greenkeeper[bot] ad72226f42 fix(package): update pg-promise to version 5.6.8 (#3800) 2017-05-12 13:04:16 -04:00
Florent Vilmart cc97067573 chore(package): update mime to version 1.3.6 (#3807) 2017-05-12 12:53:38 -04:00
greenkeeper[bot] b5c5d91754 chore(package): update cross-env to version 5.0.0 (#3804) 2017-05-12 09:24:54 -04:00
Florent Vilmart 10c7cb0bfa requiresAuthentication is self-sufficient for ACL's (#3784)
* Adds test to reproduce issue #3753

* Consider requiresAuthentication as the same level as other CLP

* Better testing
2017-05-11 11:09:06 -04:00
Anthony Mosca 877ef78b5e Improve email verification (#3681)
* Removed hidden keys from users/me.

* Ensured that general users cannot update email verified flag.

* Updated tests to reflect email verification changes.
2017-05-11 09:44:58 -04:00
Florent Vilmart 81a55e655d chore(package): update nyc to version 10.3.2 (#3785) 2017-05-11 09:42:44 -04:00
youngerong e0be653f4c Properly obfuscate query parameters in logs (#3793)
* fix-3789

* fix3789 add unit test
2017-05-11 07:17:20 -04:00
cmmills91 22ba39812b Add support for resending verification email in case of expired token (#3617)
* -Defines new public API route /apps/:appId/resend_verification_email that will generate a new email verification link and email for a user identified by username in POST body
-Add template and url support for invalidVerificationLink, linkSendSuccess, and linkSendFail pages. The invalidVerificationLink pages includes a button that allows the user to generate a new verification email if their current token has expired, using the new public API route
-All three pages have default html that will be functional out of the box, but they can be customized in the customPages object. The custom page for invalidVerificationLink needs to handle the extraction of the username and appId from the url and the POST to generate the new link (this requires javascript)
-Clicking a link for an email that has already been verified now routes to the emailVerifySuccess page instead of the invalidLink page

* Fix package.json repo url to be parse-server againwq

* Fix js lint issues

* Update unit tests

* Use arrow functions, change html page comments, use qs and a string template to construct location for invalidVerificationLink page, syntax fixes

* Remember to pass result when using arrow function
2017-05-10 09:02:16 -04:00
Jack Wearden 7b9ebc4e8e Avoid multiple $nears in one query (#3798)
Mongo has a hard limit on 1 $near operation per query. Restructuring to
avoid SERVER-13732 should not invalidate a query by creating multiple
$near operations.

Additionally, queries with multiple $ors are now recursively handled,
whereas before, ops at the top level would only have been pushed one
level deeper.

https://github.com/parse-community/parse-server/issues/3767
2017-05-10 08:32:08 -04:00
kahoona77 64e6f40779 catch unhandled rejection with installation-handling (#3795) 2017-05-09 08:10:38 -04:00
Florent Vilmart 8d67776c2e Removes un-necessary shutdown handler (#3786)
* Removes un-necessary shutdown handler

- When registering a shutdown hander, the node process has to be exited manually which causes issues for many users

* Proper graceful shutdown from CLI
2017-05-08 10:06:01 -07:00
greenkeeper[bot] 864d191d0c chore(package): update request-promise to version 4.2.1 (#3788) 2017-05-08 10:03:54 -04:00
Florent Vilmart 45a9d50110 Skip authData validation if it hasn't changed. (#3783)
* Adds test for the new feature

* Re-validate authData only if mutated

- In case of short-lived tokens (like facebook) this will allow clients to be lax with asking users to re-login
2017-05-07 12:55:30 -04:00
Serge Bornow 4f903952bf Update to package.json - mongo client (#3781)
Updated mongodb-core to 2.1.10
NODE-981 delegate auth to replset/mongos if inTopology is set.
NODE-978 Wrap connection.end in try/catch for node 0.10.x issue causing exceptions to be thrown, Also surfaced getConnection for mongos and replset.
Remove dynamic require (Issue #175, https://github.com/tellnes).
NODE-696 Handle interrupted error for createIndexes.
Fixed isse when user is executing find command using Server.command and it get interpreted as a wire protcol message, #172.
NODE-966 promoteValues not being promoted correctly to getMore.
Merged in fix for flushing out monitoring operations.
NODE-983 Add cursorId to aggregate and listCollections commands (Issue, #1510).
Mark group and profilingInfo as deprecated methods
NODE-956 DOCS Examples.
Update readable-stream to version 2.2.7.
NODE-978 Added test case to uncover connection.end issue for node 0.10.x.
NODE-972 Fix(db): don't remove database name if collectionName == dbName (Issue, #1502)
Fixed merging of writeConcerns on db.collection method.
NODE-970 mix in readPreference for strict mode listCollections callback.
NODE-966 added testcase for promoteValues being applied to getMore commands.
NODE-962 Merge in ignoreUndefined from collection level for find/findOne.
Remove multi option from updateMany tests/docs (Issue #1499, https://github.com/spratt).
NODE-963 Correctly handle cursor.count when using APM.
2017-05-06 19:52:11 -04:00
greenkeeper[bot] 0e64635dfb chore(package): update deep-diff to version 0.3.8 (#3773) 2017-05-05 09:33:50 -07:00
greenkeeper[bot] d64f100bc9 chore(package): update mongodb-runner to version 3.5.0 (#3777) 2017-05-05 09:33:37 -07:00
greenkeeper[bot] 181fe34f0b chore(package): update deep-diff to version 0.3.7 (#3769)
https://greenkeeper.io/
2017-05-01 09:06:44 -07:00
greenkeeper[bot] 4fbe3540bd chore(package): update jasmine-spec-reporter to version 4.1.0 (#3768)
https://greenkeeper.io/
2017-04-30 16:37:45 -07:00
greenkeeper[bot] c6b95482d2 chore(package): update deep-diff to version 0.3.6 (#3752)
https://greenkeeper.io/
2017-04-25 09:12:05 -07:00
greenkeeper[bot] bd4ca383fd chore(package): update jasmine to version 2.6.0 (#3749)
https://greenkeeper.io/
2017-04-24 14:35:14 -07:00
Florent Vilmart f7af48db89 Fix/3678 overloaded query constraints (#3723)
* Added failing test

* Updated test description

* Properly handle equalities with additional operator constraints

* adds continuation to silence rejected promises

* Wrap json parsing

* nits
2017-04-23 15:10:17 -07:00
Benjamin Wilson Friedman d2b5be20a8 Fixed docs.parseplatform.org links in README.md (#3728)
* Fixed docs.parseplatform.org links in README.md

Fixes a couple links in the README.md that were forcing `https` while docs.parseplatform.org does not support a secure connection currently.

* Changed link for sdks

Updated the link for the sdks to the more detailed page http://parseplatform.org/#sdks
2017-04-23 17:59:24 -04:00
Paulo Vítor S Reis 69042fbf50 [postgres] Improve performance when adding many new fields to the Schema (#3740) 2017-04-23 17:25:33 -04:00
Paulo Vítor S Reis 5e14147676 Fixes issue affecting deleting multiple fields of a Schema (#3735) 2017-04-23 17:22:55 -04:00
Natan Rolnik 2a5c20376a Merge pull request #3744 from acinader/fix-some-typos-pg-init-options-test
Some wordsmithing on test descriptions.
2017-04-24 00:12:58 +03:00
Arthur Cinader a0c1974015 Some wordsmithing on test descriptions. 2017-04-23 11:14:32 -07:00
greenkeeper[bot] 0f1aea1db1 Update dependencies to enable Greenkeeper 🌴 (#3712)
* chore(package): update dependencies

https://greenkeeper.io/

* chore(travis): whitelist greenkeeper branches 

https://greenkeeper.io/

* docs(readme): add Greenkeeper badge 

https://greenkeeper.io/

* Update README.md
2017-04-23 10:05:57 -07:00
Florent Vilmart 5813fd0bf8 Fix flaky tests (#3724)
* adds continuation to silence rejected promises

* Wrap json parsing
2017-04-16 16:50:03 -04:00
Florent Vilmart dcd8e5626a Updates npm deployment keys (#3713)
* Fix on:repo clause for travis

* Update deploy keys
2017-04-16 11:46:32 -04:00
Florent Vilmart fd375cb927 Fixes issue affecting queries on Pointer arrays (#3721)
* Transform array atoms on equality

* nits
2017-04-16 11:44:41 -04:00
Florent Vilmart 907b160fc7 Adds support for PushScheduling (#3722)
* Add support for push scheduling

Add a configuration flag on the server to handle the availability of
push scheduling.

* Update push controller to skip sending only if scheduling is configured

Only skip push sending if scheduling is configured

* Update bad conventions

* Add CLI definitions for push scheduling

* Adds tests for pushTime

* Adds test for scheduling

* nits

* Test for not scheduled
2017-04-15 17:20:55 -04:00
Florent Vilmart 302a0dda73 Prepare 2.3.8 release (#3710) 2017-04-12 16:02:13 -07:00
Florent Vilmart e7a45cecb7 Adds graceful shutdown handler for mongodb (#3706) 2017-04-11 09:26:25 -07:00
Florent Vilmart 906fb27389 Delete { 2017-04-08 18:24:44 -04:00
ren dong f3f81b692b support pg-promise init options (#3613)
* pg-promise init options

* add database init options

* Create PostgresInitOptions.spec.js

* Update PostgresInitOptions.spec.js

* Update PostgresInitOptions.spec.js

* add PostgresInitOptions test

* Add files via upload

* linebreaks CRLF to LF

* modify postgresURI to test environment

* modify pg error code to 42P01

* fix reconfigureServer callback
2017-04-07 07:50:55 -04:00
zhoul-HS 52828683ba Add __type property to GeoPoint fields in PostgresStorageAdapter (#3695)
* Add __type property to GeoPoint fields in PostgresStorageAdapter

* Add test to check __type on GeoPoint queries
2017-04-07 07:44:12 -04:00
Florent Vilmart 0add7c0c50 Fixes broken links
Fixes #3692
2017-04-06 08:27:00 -04:00
Jean-Michel Fayard 1bc74f4aa3 doc: Host sample application on flynn (#3691) 2017-04-06 07:44:36 -04:00
Florent Vilmart c2e3092385 Updates links 2017-04-05 17:22:45 -04:00
Kevin Lacker 79806c5eba transfer code 2017-04-05 13:44:18 -07:00
George Perez 48115349d4 Add -d flag to link parse with mgo container (#3677)
Needed to run docker run --name my-parse-server --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY to link the containers
2017-04-03 12:39:05 -07:00
Greenkeeper 87085b35b6 chore(package): update cross-env to version 4.0.0 (#3680)
https://greenkeeper.io/
2017-04-03 12:36:42 -07:00
Greenkeeper 65a8a1fc56 chore(package): update ws to version 2.2.3 (#3686)
https://greenkeeper.io/
2017-04-03 12:35:34 -07:00
Greenkeeper f9ecebde3b chore(package): update babel-core to version 6.24.0 (#3628)
https://greenkeeper.io/
2017-03-28 15:28:41 -07:00
Benjamin Wilson Friedman e01b417d3f Added exception for Twitter and OAuth missing options (#3676)
* Added exception for Twitter and OAuth missing configuration information

* Updated error codes to INTERNAL_SERVER_ERROR, code 1
2017-03-28 18:16:47 -04:00
Zubeen Tolani a54f4d43fa Add install instructions for root user (#3419)
* Add install instructions for root user

* Reword the comment

* Update README.md

Commit adds a reference to solve the EACCES error while installation.

* remove trailing whitespace
2017-03-27 14:30:55 -07:00
Greenkeeper 7a43faa49c chore(package): update ws to version 2.2.2 (#3656)
https://greenkeeper.io/
2017-03-25 10:31:03 -07:00
Greenkeeper abf6828a7f chore(package): update babel-plugin-syntax-flow to version 6.18.0 (#3664)
https://greenkeeper.io/
2017-03-25 10:30:15 -07:00
Greenkeeper 2436a85de6 chore(package): update uws to version 0.14.0 (#3670)
https://greenkeeper.io/
2017-03-25 10:29:35 -07:00
Florent Vilmart 1f9a4edacd Ignore duplicate_object error on create _Schema if not exists (#3668) 2017-03-23 22:29:43 -04:00
Greenkeeper 6a2b972566 chore(package): update tv4 to version 1.3.0 (#3653)
https://greenkeeper.io/
2017-03-20 13:10:31 -04:00
Florent Vilmart f3765e0b8e use nyc for coverage (#3652)
* use nyc for coverage

* removes unnecessary logss

* Disable RedisCacheAdapter tests when not in specific environment
2017-03-20 09:58:36 -07:00
Greenkeeper eeb3a7028d chore(package): update mongodb to version 2.2.25 (#3649)
https://greenkeeper.io/
2017-03-17 18:57:36 -04:00
Wissam Abirached 2533a8cdb3 Do not create user if username or password is empty (#3650) 2017-03-17 18:57:21 -04:00
Greenkeeper ea94ae73f6 chore(package): update request-promise to version 4.2.0 (#3648)
https://greenkeeper.io/
2017-03-16 18:28:15 -04:00
Greenkeeper 1ec70b299b chore(package): update redis to version 2.7.1 (#3644)
https://greenkeeper.io/
2017-03-15 09:00:37 -04:00
Greenkeeper 45efe2f29c chore(package): update cross-env to version 3.2.4 (#3642)
https://greenkeeper.io/
2017-03-15 09:00:19 -04:00
Stefan Trauth 5e29336962 update push adapter to 1.3.0 (#3641) 2017-03-15 08:59:58 -04:00
jeacott1 82e0a59139 missing return in test. (#3645) 2017-03-15 08:59:19 -04:00
Greenkeeper 51b519355a chore(package): update ws to version 2.2.1 (#3631)
https://greenkeeper.io/
2017-03-13 15:48:20 -04:00
Greenkeeper 08e8a428b6 chore(package): update babel-cli to version 6.24.0 (#3627)
https://greenkeeper.io/
2017-03-13 15:48:09 -04:00
Greenkeeper 63bd333715 chore(package): update babel-preset-es2015 to version 6.24.0 (#3630)
https://greenkeeper.io/
2017-03-13 14:17:36 -04:00
Greenkeeper 11071dd015 chore(package): update babel-register to version 6.24.0 (#3629)
https://greenkeeper.io/
2017-03-13 14:17:22 -04:00
Greenkeeper 9b879dd371 chore(package): update winston-daily-rotate-file to version 1.4.6 (#3625)
https://greenkeeper.io/
2017-03-13 14:17:10 -04:00
Florent Vilmart eb1b84b556 CLI: Do not exit(0) so each module can close its connections (#3634) 2017-03-13 12:13:07 -04:00
Greenkeeper 80ec61f7ce chore(package): update redis to version 2.7.0 (#3620)
https://greenkeeper.io/
2017-03-12 21:28:01 -07:00
Greenkeeper 9f1077c9d3 chore(package): update pg-promise to version 5.6.4 (#3622)
https://greenkeeper.io/
2017-03-12 17:17:18 -07:00
Steven Shipton 593a7a6264 Release 2.3.7 2017-03-10 18:05:13 +00:00
steven-supersolid 4addf9dfa9 Update version to 2.3.7 2017-03-10 17:49:55 +00:00
Steven Shipton 2547fd08ae Update CHANGELOG.md
style nits
2017-03-10 17:15:34 +00:00
Steven Shipton 581f919d1a Changelog 2.3.7 2017-03-10 17:12:56 +00:00
Steven Shipton 0b1a61cd85 Merge pull request #3614 from ParsePlatform/greenkeeper-request-2.81.0
Update request to version 2.81.0 🚀
2017-03-10 16:37:00 +00:00
greenkeeperio-bot 943b84b13f chore(package): update request to version 2.81.0
https://greenkeeper.io/
2017-03-09 08:09:13 -08:00
Steven Shipton 70f60267c5 upgrade body-parser and express (#3605) 2017-03-07 08:53:53 -08:00
Greenkeeper f0f04d6351 chore(package): update body-parser to version 1.17.0 (#3589)
https://greenkeeper.io/
2017-03-05 09:11:53 -08:00
Arthur Cinader 1d8edd73c3 default for userSensitiveFields should be an array not a string. (#3599) 2017-03-05 09:06:45 -08:00
Greenkeeper 8d802b9461 chore(package): update request to version 2.80.0 (#3596)
https://greenkeeper.io/
2017-03-04 15:57:37 -08:00
Vitaly Tomilov 271608bc82 Update PostgresStorageAdapter.js (#3578)
* Update PostgresStorageAdapter.js

proper database API, via transaction.

* Update PostgresStorageAdapter.js

fixing bracket.

* Update PostgresStorageAdapter.js

adding the same rejection approach as before, the functionality remains identical.

* Update PostgresStorageAdapter.js

* Update PostgresStorageAdapter.js

lint fixing

* Update PostgresStorageAdapter.js

fixing misspelling.
2017-03-04 15:56:53 -08:00
Greenkeeper 5c5ef0313b chore(package): update ws to version 2.2.0 (#3580)
https://greenkeeper.io/
2017-03-04 17:50:54 -06:00
Xy Ziemba 29fec01a42 Resend Verification Email Endpoint (#3543)
* Endpoint to Handle Verification Email Request

* Adds tests for verificationEmailRequest endpoint

* Better error responses for `/verificationEmailRequest`
2017-03-04 13:30:52 -08:00
Greenkeeper 92b699920e chore(package): update express to version 4.15.0 (#3590)
https://greenkeeper.io/
2017-03-04 13:27:59 -08:00
Natan Rolnik 9bfa0c60c4 Adds index on _Role name property (#3586)
* Adds index on _Role name property

In order to avoid having different _Role objects with the same name, adding an index on the name property of _Role is necessary.

Fixes #3579

* Uses throw instead of Promise.reject when enforcing unique indexes

* Fixes wrong sorting of results in schemas tests
2017-03-04 12:42:19 -08:00
Greenkeeper 0181fb51b3 chore(package): update winston-daily-rotate-file to version 1.4.5 (#3594)
https://greenkeeper.io/
2017-03-03 11:57:25 -08:00
Ryan Foster e6006e800e adding TTL option for redis cache adapter (#3397)
* adding TTL option for redis cache adapter

* adding test for RedisCacheAdapter

* Fixing adapater var name

* changing timeout

* updating default time

* Fix the redis cache spec to construct the cache with the anticipated ttl

make timeout values really really small so our test run fast :).
2017-02-27 12:29:15 -08:00
Florent Vilmart a45d1848db version 2.3.6 (#3574)
* version 2.3.6

* Update CHANGELOG.md
2017-02-27 19:10:06 +02:00
Greenkeeper 6381e15e6d chore(package): update pg-promise to version 5.6.2 (#3573)
https://greenkeeper.io/
2017-02-27 11:45:43 -05:00
Jack Wearden 73195627c0 Alleviate SERVER-13732 on all top level filters (#3564)
In a prior commit, improvements were made to the addition of `_rperm`
in the case of `$or` queries, to avoid MongoDB bug SERVER-13732.

As the vast majority of $or queries previously hit this bug due to the
presence of `_rperm` on most Parse queries), the present solution
avoids the bug and improves query performance in most cases.

However, it's still possible for clients to supply their own queries
which hit that bug, such as those with `_created_at` or `_updated_at`
filters, or their own properties from their data model.

This commit makes the logic currently present for `_rperm` available
to all top level filters that exist alongside an $or query, meaning
SERVER-13732 should be avoided in all cases where keys at the top and
inner levels do not have name clashes.

- https://github.com/ParsePlatform/parse-server/pull/3476
- https://jira.mongodb.org/browse/SERVER-13732
2017-02-26 13:37:33 -05:00
Greenkeeper 053bbefda9 chore(package): update pg-promise to version 5.6.1 (#3572)
https://greenkeeper.io/
2017-02-26 13:34:19 -05:00
Paulo Vítor S Reis 032d5e55ca Logger MaskSentive just for strings and array string items (#3571) 2017-02-26 13:34:03 -05:00
shaukatnamal fe5947ce4c chore(package): update eslint to version 3.16.1 (#3563)
https://greenkeeper.io/
2017-02-24 16:18:57 -08:00
Bhaskar Reddy Yasa 0e900cbefd allow flow through to passwordPolicy in case of empty ('') password (#3560) 2017-02-24 07:21:50 -05:00
Florent Vilmart 41358d2226 Adds ability to pass a middleware to CLI for instrumentation (#3554)
* Adds ability to pass a middleware to CLI for instrumentation

* Adds readme
2017-02-22 23:42:21 +02:00
Greenkeeper 73260897cd chore(package): update pg-promise to version 5.5.8 (#3551)
https://greenkeeper.io/
2017-02-21 14:28:12 -08:00
Florent Vilmart d3e6c0dea3 Adds test for #3544 (#3545) 2017-02-21 15:06:45 -05:00
Florent Vilmart b4e27e1160 Adds testing safeguard on lib (#3537) 2017-02-20 22:45:45 +02:00
Greenkeeper 79b0c790f0 chore(package): update uws to version 0.13.0 (#3542)
https://greenkeeper.io/
2017-02-20 12:26:44 -08:00
Greenkeeper 64f608ec9f chore(package): update ws to version 2.1.0 (#3526)
https://greenkeeper.io/
2017-02-20 12:25:39 -08:00
Tyler Brock 67260b5fe0 Factor out checking for triggers/liveQuery in rest.js (#3539) 2017-02-20 12:25:25 -08:00
Florent Vilmart 411daf4d41 Release 2.3.5 2017-02-19 11:34:42 -05:00
Natan Rolnik 7a3c9cd33a Changelog 2.3.4 (#3533) 2017-02-19 08:44:33 -05:00
Florent Vilmart 193e5a4278 Make sure we don't treat dot notation keys as topLevel atoms (#3531)
Fixing GeoPoints and Files in _GlobalConfig
2017-02-19 12:07:54 +02:00
Jeremy Louie 6ae0675010 Use flushdb instead of flushall in RedisCacheAdapter (#3523) 2017-02-18 19:36:43 -05:00
David Starke a520ea0243 Fix LiveQuery unsafe user (#3525)
* LiveQuery should not use unsafe user setting

* server should issue queries with the master key
2017-02-18 19:36:20 -05:00
Greenkeeper 58bdeeee2d chore(package): update pg-promise to version 5.5.7 (#3528)
https://greenkeeper.io/
2017-02-18 19:24:32 -05:00
Vitaly Tomilov fc7f9f76f5 removing inline sql. (#3529)
* removing inline sql.

* Update index.js

trying to fix indentation.

* Update index.js

commenting out console output for errors.

* Rename json-object-set-key.sql to json-object-set-keys.sql

* Update index.js

* Update add-unique.sql

correcting sql.

* Update add.sql

correcting sql.

* Update contains-all.sql

correcting sql.

* Update contains.sql

correcting sql.

* Update remove.sql

correcting sql.

* Update json-object-set-keys.sql

fixing sql reserved words.
2017-02-18 14:26:25 -05:00
Greenkeeper ef8d534679 chore(package): update babel-core to version 6.23.1 (#3512)
https://greenkeeper.io/
2017-02-17 10:18:12 -08:00
Greenkeeper 1c9852e53f chore(package): update babel-core to version 6.23.0 (#3509)
https://greenkeeper.io/
2017-02-17 10:16:43 -08:00
Greenkeeper 3f0de0c2ed chore(package): update babel-register to version 6.23.0 (#3510)
https://greenkeeper.io/
2017-02-17 10:14:27 -08:00
Greenkeeper eccdeccb59 chore(package): update babel-cli to version 6.23.0 (#3511)
https://greenkeeper.io/
2017-02-17 10:14:10 -08:00
Greenkeeper f16f8423db chore(package): update mongodb to version 2.2.24 (#3513)
https://greenkeeper.io/
2017-02-17 10:13:55 -08:00
Greenkeeper bd60a5c1d1 chore(package): update body-parser to version 1.16.1 (#3499)
https://greenkeeper.io/
2017-02-11 15:03:05 -08:00
Robin Naundorf 40b9f953ca Fix small display errors (#3500)
* Add missing '$' to declare variable
* Move \n to correct position
2017-02-11 12:24:09 -05:00
Héctor Ramos 15eafd4dcc Update ISSUE_TEMPLATE.md 2017-02-10 11:56:58 -08:00
Héctor Ramos 11cde12245 Merge pull request #3497 from acinader/allow-empty-client-key
Allow empty client key
2017-02-09 16:16:49 -08:00
Arthur Cinader 5861996cb0 explicitly check if auth keys are undefined
Simply checking if they are truthy causes a false negative
if the value is ''.
2017-02-09 15:20:10 -08:00
Arthur Cinader ca70ad0141 Add a unit test to fail when clientKey='' 2017-02-09 14:50:28 -08:00
Natan Rolnik 5a22df13a5 Changelog 2.3.3 (#3493) 2017-02-09 17:13:15 +02:00
Greenkeeper 5b7144176c chore(package): update ws to version 2.0.3 (#3488)
https://greenkeeper.io/
2017-02-08 10:43:00 -08:00
Arthur Cinader 4979503f63 Two tests that fail often. Give em a little more time. (#3453) 2017-02-08 10:06:42 -08:00
Greenkeeper e32a37c2ec chore(package): update bcryptjs to version 2.4.3 (#3485)
https://greenkeeper.io/
2017-02-08 09:09:29 -08:00
Florent Vilmart f7f17cdb23 Update package.json (#3480)
* Update package.json

* Update .travis.yml
2017-02-06 10:41:33 -07:00
nodechefMatt ecf422b2d9 Mongo object to Parse object date serialization - avoid re-serialization of iso of type Date (#3389)
* Mongo object to Parse object date serialization - avoid nested ios

* Mongo object to Parse object date serialization

* Remove file from previous commit
2017-02-04 08:32:35 -08:00
Greenkeeper 49dc8df1de chore(package): update babel-cli to version 6.22.2 (#3411)
https://greenkeeper.io/
2017-02-03 21:40:32 -08:00
Greenkeeper c3c5f06efd chore(package): update babel-register to version 6.22.0 (#3409)
https://greenkeeper.io/
2017-02-03 21:40:21 -08:00
Greenkeeper 2a454fec35 chore(package): update babel-preset-es2015 to version 6.22.0 (#3408)
https://greenkeeper.io/
2017-02-03 21:40:07 -08:00
Greenkeeper 01b4c4e0cb chore(package): update babel-plugin-transform-flow-strip-types to version 6.22.0 (#3406)
https://greenkeeper.io/
2017-02-03 21:39:18 -08:00
Greenkeeper 3714d68aaf chore(package): update babel-preset-stage-0 to version 6.22.0 (#3407)
https://greenkeeper.io/
2017-02-03 21:38:49 -08:00
Greenkeeper 77daa3ca18 chore(package): update ws to version 2.0.2 (#3475)
https://greenkeeper.io/
2017-02-03 21:37:24 -08:00
Greenkeeper 0b574f04a9 chore(package): update babel-core to version 6.22.1 (#3412)
https://greenkeeper.io/
2017-02-03 21:37:05 -08:00
Greenkeeper a89a30182e chore(package): update mongodb to version 2.2.22 (#3426)
https://greenkeeper.io/
2017-02-03 21:36:53 -08:00
Jack Wearden 96a9588a6e MongoDB $or Queries avoid SERVER-13732 bug (#3476)
MongoDB has an unfixed bug in all supported versions 2.6-3.4 which
results in suboptimal index usage for `$or` queries when the query has
implicit `$and`s at the query root.

When adding `_rperm` to `$or` queries, Parse accidentally creates
queries which hit this bug.

The fix in this commit applies the suggested workaround of putting the
`_rperm` property inside all `$or` subdocuments, moving it from the top
level and leaving `$or` as the only top-level operator.

MongoDB Bug Link: https://jira.mongodb.org/browse/SERVER-13732
2017-02-03 21:34:19 -08:00
Greenkeeper 66cdfaed4b chore(package): update ws to version 2.0.1 (#3465)
https://greenkeeper.io/
2017-02-02 14:05:05 -08:00
Greenkeeper 9262147a6f chore(package): update pg-promise to version 5.5.6 (#3461)
https://greenkeeper.io/
2017-02-02 12:00:07 -08:00
Greenkeeper 8b4e9ca637 chore(package): update multer to version 1.3.0 (#3434)
https://greenkeeper.io/
2017-02-02 11:52:20 -08:00
Greenkeeper 2160d10990 chore(package): update redis to version 2.6.5 (#3377)
https://greenkeeper.io/
2017-02-02 09:10:21 -08:00
Arthur Cinader e7f2995143 Adjust a test for change to logging. (#3452) 2017-01-30 13:28:52 -05:00
Arthur Cinader d5940b17b6 Add logging on failure to create file (#3424) 2017-01-30 12:36:30 -05:00
Arthur Cinader f864141663 Log Parse Errors so they are intelligible. (#3431)
The problem this pr is trying to solve:

When an error occurs on the server, a message should
be returned to the client, and a message should be logged.

Currently, on the server, the log is just [object, object]

This pr will stop calling the default express error handler
which causes two problems: 1. it writes to console instead of log file
2. the output is completely useless! :)

Instead, we'll log the error ourselves using the ParseServer's logger.

fixes: #661
2017-01-30 12:32:54 -05:00
Elliot Blackburn 711db9ccd2 Remove duplicate word from the README log section (#3450)
In the "Logging" section it originally read: "Parse Server, will, by default, will log:", I've removed the second "will" to make "Parse Server will, by default, log:".
2017-01-30 14:39:18 +02:00
Greenkeeper 6a6829ce80 chore(package): update express to version 4.14.1 (#3445)
https://greenkeeper.io/
2017-01-29 15:08:47 -05:00
Ricardo Matsui d67be492f5 Verify pushes are sent to the expected number of installations (#3418) 2017-01-28 11:10:21 -08:00
Greenkeeper ea45f69af6 chore(package): update winston-daily-rotate-file to version 1.4.4 (#3442)
https://greenkeeper.io/
2017-01-28 09:18:21 -08:00
Greenkeeper 43c3d8e2a2 chore(package): update winston to version 2.3.1 (#3414)
https://greenkeeper.io/
2017-01-23 10:29:40 -07:00
Greenkeeper d8a1a614e8 chore(package): update body-parser to version 1.16.0 (#3391)
https://greenkeeper.io/
2017-01-19 13:42:17 -08:00
Greenkeeper 553db0ada5 chore(package): update pg-promise to version 5.5.2 (#3379)
https://greenkeeper.io/
2017-01-19 11:01:03 -08:00
Greenkeeper 388a59d816 chore(package): update pg-promise to version 5.5.1 (#3362)
https://greenkeeper.io/
2017-01-19 11:00:34 -08:00
Greenkeeper de4c1ee418 Update mongodb to version 2.2.21 🚀 (#3368)
* chore(package): update mongodb to version 2.2.20

https://greenkeeper.io/

* Fix race when fetching schema frequently

* Bumps mongo to 2.2.21
2017-01-16 13:26:14 -08:00
Florent Vilmart cb15c0b616 Disallow failures for PG (#3372) 2017-01-16 13:24:45 -08:00
Greenkeeper 8dd864a033 chore(package): update parse-server-push-adapter to version 1.2.0 (#3386)
https://greenkeeper.io/
2017-01-16 11:27:19 -05:00
Florent Vilmart c26db2248f use uWS as optional dependency for ws server (#3231)
* use uWS as optional dependency for ws server

* Update ParseWebSocketServer.js
2017-01-14 07:43:31 -08:00
Greenkeeper b2a3479dbf chore(package): update winston-daily-rotate-file to version 1.4.3 (#3369)
https://greenkeeper.io/
2017-01-14 07:40:57 -08:00
Florent Vilmart deedf7b370 Push scalability (#3080)
* Update status through increment
* adds support for incrementing nested keys
* fix issue when having spaces in keys for ordering
* Refactors PushController to use worker
* Adds tests for custom push queue config
* Makes PushController adapter independant
* Better logging of _PushStatus in VERBOSE
2017-01-13 19:34:04 -05:00
Greenkeeper 5f849ca662 chore(package): update redis to version 2.6.4 (#3364)
https://greenkeeper.io/
2017-01-12 21:45:14 -05:00
Greenkeeper 0279b5e5f3 chore(package): update jasmine to version 2.5.3 (#3358)
https://greenkeeper.io/
2017-01-11 18:54:23 -08:00
Arthur Cinader 4cb6e7d209 Add lint rule space-infix-ops (#3237)
Disallows: 1+1.  Must be 1 + 1.
2017-01-11 12:31:40 -08:00
Greenkeeper 56bb505df2 chore(package): update jasmine-spec-reporter to version 3.1.0 (#3344)
https://greenkeeper.io/
2017-01-11 12:31:15 -08:00
Arthur Cinader ceb5a1b5f0 chore(package): update jasmine-spec-reporter to version 3.0.0 (#3333)
https://greenkeeper.io/

Fix parse-server to work with jasmine-spec-reporter-3.0.0
2017-01-11 11:53:24 -08:00
Natan Rolnik 0958b15297 Changelog 2.3.2 (#3343)
* Updates package.json: version to 2.3.2

* Updates Changelog for 2.3.2
2017-01-09 15:46:34 +02:00
Lenart Rudel 5d9dbea07b Add parseFrameURL for masking user-facing pages (#3267)
* Add parseFrameURL for masking user-facing pages.

Allow users to specify a different address which is used to mask parse
requests for verifying email and resetting password. This is how Parse.com
used to allow customers to gain control over page content, styling etc.

On the destination page javascript is used to check the link in the request
and embed the parse server page using IFRAME.

* Fix code indentation

* Rename method for building link and pass config to it.

* Add customPages options to README.md.

* Add tests for parseFrameURL email link building, and parseFrameURL option.

* Add parseFrameURL for masking user-facing pages.

Allow users to specify a different address which is used to mask parse
requests for verifying email and resetting password. This is how Parse.com
used to allow customers to gain control over page content, styling etc.

On the destination page javascript is used to check the link in the request
and embed the parse server page using IFRAME.

* Fix code indentation

* Rename method for building link and pass config to it.

* Add customPages options to README.md.

* Don't Object.assign to defaultConfiguration global
2017-01-08 10:56:57 -08:00
Bhaskar Reddy Yasa f331f6644c Add support for regex string for password policy validatorPattern setting (#3331) 2017-01-08 07:12:44 -08:00
David Starke df029b82eb LiveQuery should match subobjects with dot notation (#3322)
* LiveQuery should match subobjects with dot notation

* one additional test case
2017-01-06 17:06:25 -08:00
Arthur Cinader edba550cf6 winston-daily-roate-file-1.4.2 makes a change where timestamp is not on by default anymore. (#3335)
see: https://github.com/winstonjs/winston-daily-rotate-file/commit/aa28f522718778fe8210ced1090353ffc29c6f31

pretty incredible that they would release this as a patch release, but my fix is solid and defensive

chore(package): update winston-daily-rotate-file to version 1.4.2

https://greenkeeper.io/
2017-01-06 16:32:03 -08:00
Greenkeeper c7775eb0b9 chore(package): update cross-env to version 3.1.4 (#3308)
https://greenkeeper.io/
2017-01-06 14:50:09 -08:00
Greenkeeper 0b44820a7d chore(package): update winston-daily-rotate-file to version 1.4.1 (#3317)
https://greenkeeper.io/
2017-01-06 13:20:12 -08:00
Greenkeeper b7322064d8 chore(package): update pg-promise to version 5.5.0 (#3224)
https://greenkeeper.io/
2017-01-04 09:41:21 -05:00
jeacott1 ac3f5cd686 this function was extremely slow. (#3264)
this fix reduces the time to process 165k installations
from 282738.599ms (almost 5 minutes of total server lockup!)
to just 16.283ms!
2017-01-04 09:27:47 -05:00
Greenkeeper 9fd34fd25e chore(package): update bcryptjs to version 2.4.0 (#3225)
https://greenkeeper.io/
2017-01-03 16:42:59 -06:00
Greenkeeper 7ff3a3813d chore(package): update babel-core to version 6.21.0 (#3249)
https://greenkeeper.io/
2017-01-02 15:04:18 -06:00
Greenkeeper 754f5d0505 chore(package): update babel-plugin-transform-flow-strip-types to version 6.21.0 (#3250)
https://greenkeeper.io/
2017-01-02 14:40:30 -06:00
Greenkeeper 0e8c3c2457 chore(package): update multer to version 1.2.1 (#3276)
https://greenkeeper.io/
2017-01-02 14:09:24 -06:00
Greenkeeper 423a50133a chore(package): update lodash to version 4.17.4 (#3303)
https://greenkeeper.io/
2017-01-02 13:25:37 -06:00
Greenkeeper a3d416de4a chore(package): update bcrypt to version 1.0.2 (#3304)
https://greenkeeper.io/
2017-01-01 12:01:59 -06:00
Greenkeeper a2dfcc8da8 chore(package): update babel-core to version 6.20.0 (#3216)
https://greenkeeper.io/
2017-01-01 08:44:38 -06:00
Arthur Cinader f4734a65c0 fix trivial typo (#3238) 2016-12-14 14:28:23 -08:00
Florent Vilmart aa071e034e Changelog 2.3.1 (#3213)
* Update CHANGELOG.md

* Update package.json
2016-12-08 14:30:59 -05:00
Florent Vilmart 4f46eceacf Fixes issue affecting the auth providers (#3211)
* Reproduces error for issue 3208

* Make sure we don't override a default adapter with the options when validation function are not provided
2016-12-08 08:54:06 -08:00
Florent Vilmart 81ebbb8954 Update README.md 2016-12-08 10:16:31 -05:00
Greenkeeper e82395e268 chore(package): update bcrypt to version 1.0.1 (#3207)
https://greenkeeper.io/
2016-12-08 09:55:35 -05:00
Florent Vilmart 0159360a5d Fixes CI dist (#3206) 2016-12-07 19:57:13 -08:00
Florent Vilmart aab10bea79 Changelog 2.3.0 (#3205)
* 2.3.0 changelog

* bumps package

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update CHANGELOG.md
2016-12-07 20:48:54 -05:00
Florent Vilmart 752f0e9143 Fixes #3195 (#3201)
* Adds error reproduction

* Fix transform in order to accept nested array results in $in/$nin

The error originated by the nesting of 2 array in $in [["..."]], using _.flatMap with those will guarantee at the lower level
that the query is properly resolved

* nits

* _.flatMap the $in/$nin values
2016-12-07 20:03:40 -05:00
Florent Vilmart 025e7a3218 Adds depreciation notes for Parse.Cloud.useMasterKey(), (#3204)
Adds depreciation notes for Parse.Cloud.useMasterKey()
2016-12-07 18:35:22 -05:00
Arthur Cinader ca286b7108 Enable prefer-const lint rule (#3202) 2016-12-07 18:17:05 -05:00
Florent Vilmart a6c988176e Adds tests that shows issue #3194 is ok (#3200) 2016-12-07 14:18:52 -08:00
corban baxter e069b20ef9 adding simple test for adding and removing users on a role. (#3198) 2016-12-07 16:15:01 -05:00
Arthur Cinader ae30ec647b let's see if i can force latest to rebuild... (#3196) 2016-12-07 08:57:01 -08:00
Greenkeeper fc53fa9c32 chore(package): update parse-server-s3-adapter to version 1.0.6 (#3192)
https://greenkeeper.io/
2016-12-06 16:56:26 -08:00
Florent Vilmart c1dcaf1271 Auth Adapters refactoring (#3177)
* Moves all authentication providers to Adapter/Auth

* refactors specs

* Deprecates oauth option in favor of auth option

- Deprecates facebookAppIds option (in favor of auth.facebook.appIds)
- Adds warnings about the deprecated options

* nits
2016-12-06 14:09:43 -08:00
Florent Vilmart a9067260fc adds ability to pass custom installationId to create sessions in cloud code (#3156) 2016-12-06 16:46:09 -05:00
Florent Vilmart 2e569ca7dd Fixes SchemaController data for Volatile Classes (#3171)
* Reproduction for the issue

* Ensures Volatile classes and other schema share the same structure
2016-12-06 16:42:54 -05:00
Arthur Cinader 8111c3338e turn on max empty line rule with default of max: 2. (#3176)
Run with fix.
2016-12-06 13:35:23 -08:00
Florent Vilmart fb36dfa26f Fix #3185 (#3186)
* Adds tests that reproduce the issue

* Use values from keys to force include when needed
2016-12-06 16:28:55 -05:00
Andrew Lane 998b271994 HyperDev changed its name to Gomix, so reflect that in the README (#3191) 2016-12-06 10:44:37 -08:00
Florent Vilmart a7821f6e57 Removes NodeChef (#3188)
NodeChef explicitly runs a private fork of parse-server, we should not support this.

Explicitely, Scheduled Push and Cloud Code browser are most requested features, that they didn't provide back to the community.
2016-12-06 10:39:21 -05:00
Greenkeeper ddb6f7bc57 chore(package): update bcrypt to version 1.0.0 (#3178)
https://greenkeeper.io/
2016-12-05 19:08:46 -08:00
Kulshekhar Kabra 8124bf4600 Add a CLI option to specify the host (#3180) 2016-12-05 08:50:46 -05:00
Kulshekhar Kabra df74a1f8ae Use trusty in travis (#3170)
* Use trusty in travis

* Install postgresql-9.5-postgis-2.3 in travis
2016-12-03 18:43:30 -05:00
Florent Vilmart e0704b440c Adds class level permission requiring authenticated user (#893)
* Adds class level permission requiring authenticated user

* Updates to latest schema permissions syntax

* fix flaky test

* Exclude PG

* Rebased and nitted

* lints
2016-12-02 19:47:33 -05:00
Arthur Cinader 01b05b060f Strip personally identifiable information from user table for unauthorized users.
- add a config option to explicitly enumerate pii fields beyond email
- in query controller, strip pii of user table results before sending out the door.
2016-12-02 19:39:09 -05:00
Arthur Cinader a270632570 More lint tweaking (#3164)
1. Add no space in paren rule
2. fix spec/eslintrc.json so it allow for inheriting from root rc.

Because the spce rc specified reccomended, it "turned off" all of the
rule tweaks in the root.  This fixes that.
2016-12-02 19:11:54 -05:00
Florent Vilmart b9afccd338 Makes sudo: false (#3166)
* Makes sudo: false

* ignore error when configuring postgis

* Use precise instead of trusty
2016-12-02 17:53:32 -05:00
Arthur Cinader 37daafc7b0 Add strip whitespace and trailing eol to spec too. (#3159) 2016-12-01 21:45:49 -05:00
Arthur Cinader 8359284329 Fix some random minor typos. (#3157) 2016-12-01 20:34:39 -05:00
Greenkeeper 2b0c39caaa chore(package): update pg-promise to version 5.4.5 (#3140)
https://greenkeeper.io/
2016-12-01 17:59:10 -05:00
Arthur Cinader 11f7712651 Add no trailing whitespace and force eol at end of file. (#3154) 2016-12-01 13:24:46 -05:00
Arthur Cinader 7292fa7f11 Allow select (keys) to be altered in triggers (#3146)
Inspect the keys when a query is returned from a trigger and respect the new
value.
2016-12-01 10:04:47 -05:00
Arthur Cinader 4d17bed374 lint before test. (#3147)
Also turn on lint cacheing to make this change painless ;)
add lint cache to gitignore...
2016-11-30 17:58:59 -05:00
Steven Shipton 143bd11638 Change arg check from truthy to defined (#3143)
Show log on master when using cluster
2016-11-30 08:48:49 -05:00
Bhaskar Reddy Yasa bd1689190f Adds password history support to passwordPolicy (#3102)
* password history support in passwordPolicy

* Refactor RestWrite.transformUser

* fix eslint issues
2016-11-29 09:01:52 -08:00
Greenkeeper 08bac9f790 chore(package): update pg-promise to version 5.4.4 (#3121)
https://greenkeeper.io/
2016-11-29 12:01:49 -05:00
Greenkeeper f2df0303b2 chore(package): update winston-daily-rotate-file to version 1.4.0 (#3132)
https://greenkeeper.io/
2016-11-29 10:53:24 -05:00
Steven Shipton fbb2b086af ensure batch routing paths are posix (#3138) 2016-11-29 09:52:17 -05:00
Florent Vilmart 127629d4f7 Changelog 2.2.25 (#3134) 2016-11-28 16:42:41 -05:00
Florent Vilmart 6676c6a4b9 Let multiple installations be updates if no critical values are set (#3040)
* Let multiple installations be updates if no critical values are set

* nits
2016-11-28 13:11:54 -05:00
Florent Vilmart b6fb3d8d51 Update CONTRIBUTING.md (#3109)
Note on linting
2016-11-28 10:38:55 -05:00
Antonio Davi Macedo Coelho de Castro 6264f99ad5 Concat appId to PubSub channel (#3043) 2016-11-28 09:15:21 -05:00
Greenkeeper ee9099d6ad chore(package): update lru-cache to version 4.0.2 (#3127)
https://greenkeeper.io/
2016-11-28 09:14:17 -05:00
jonas-db c83a787510 Use the id of the account instead of the application id (#3108)
* Use the id of the account instead of the application id

Fix for issue: https://github.com/ParsePlatform/parse-server/issues/3106

* Update twitter.js

* Update twitter.js
2016-11-25 14:05:41 -05:00
Yuki Takeichi 85567310d4 Ignores createdAt when update (#3111) 2016-11-25 09:20:06 -05:00
David Poetzsch-Heffter 8f1c1f419b fixing equals on array columns in live query (#3089) 2016-11-24 20:42:53 -05:00
Florent Vilmart d800ff85e2 Enhanced support for batch endpoints (#3042)
* Allow to have different endpoint on publicserverURL and serverURL when batching

* nits
2016-11-24 20:14:12 -05:00
Florent Vilmart 8c2c76dd26 Adds liniting into the workflow (#3082)
* initial linting of src

* fix indent to 2 spaces

* Removes unnecessary rules

* ignore spec folder for now

* Spec linting

* Fix spec indent

* nits

* nits

* no no-empty rule
2016-11-24 15:47:41 -05:00
Florent Vilmart 6e2fba4ae4 Moves test runner logic to helper (#3041) 2016-11-24 15:09:11 -05:00
haifeizhang e4a7fbbf98 add login with qq, wechat and weibo (#3069)
* add login with qq, wechat and weibo

* modify the code style, etc
2016-11-22 08:30:31 -05:00
Bhaskar Reddy Yasa edb7b70ced Adds password expiry support to password policy (#3068)
* Adding support for password expiry policy

* Renamed daysBeforeExpiry -> maxPasswordAge
2016-11-21 07:46:38 -08:00
Florent Vilmart 11c6170ed1 Adds support for postgres when saving relation #3074 (#3084) 2016-11-21 09:22:16 -05:00
Andrew Imm d137e27eb9 Properly expose TestUtils through top-level package (#3086) 2016-11-21 08:53:16 -05:00
Antonio Davi Macedo Coelho de Castro a28257b2b7 Enabling web hooks (#3044) 2016-11-20 18:39:47 -05:00
Diwakar Cherukumilli f22cea5ca9 Merge pull request #3083 from ParsePlatform/greenkeeper-pg-promise-5.4.3
Update pg-promise to version 5.4.3 🚀
2016-11-20 15:32:10 -08:00
greenkeeperio-bot 0bc53753b0 chore(package): update pg-promise to version 5.4.3
https://greenkeeper.io/
2016-11-20 10:35:51 -08:00
Glen Tregoning 1eff210a51 MongoStorageAdapter.findOneAndUpdate returns Parse Object (#3053) (#3064) 2016-11-20 09:02:49 -05:00
Yuki Takeichi 4ea455b20a support Relation object saving (#3074)
* support Parse.Relation object save

* prevent Relation object from being saved in storage
2016-11-20 09:00:33 -05:00
Greenkeeper e9e6a4764e chore(package): update pg-promise to version 5.4.2 (#3081)
https://greenkeeper.io/
2016-11-20 08:58:43 -05:00
Diwakar Cherukumilli 4785891e99 Merge pull request #3071 from ParsePlatform/greenkeeper-request-2.79.0
Update request to version 2.79.0 🚀
2016-11-18 14:41:54 -08:00
greenkeeperio-bot 8e1e89b794 chore(package): update request to version 2.79.0
https://greenkeeper.io/
2016-11-18 09:33:12 -08:00
Bhaskar Reddy Yasa cf6ce5b9a3 Adding support for optional Password Policy (#3032)
* adds resetTokenValidityDuration setting

* adds a validator to validate password that can be used to enforce strong
passwords

* adds unit tests for passwordPolicy.validator

* adds unit tests to to fail reset password function if password is not in a valid format

* updates README.md for passwordPolicy

* prevents duplicate check for password validator in updateUserPassword

* adds optional setting to disallow username in password

* updates test cases to use fdescribe instead of describe

* updates test cases to use request-promise instead of request

* adds ability to use a RegExp or Callback function or both for a passwordPolicy.validator

* expect username parameter in redirect to password_reset_success

* adds support for _perishable_token_expires_at in postgres
2016-11-17 08:37:51 -08:00
Diwakar Cherukumilli 6be9ee5491 Merge pull request #3061 from ParsePlatform/greenkeeper-lodash-4.17.2
Update lodash to version 4.17.2 🚀
2016-11-16 12:36:14 -06:00
greenkeeperio-bot fe5f0a8980 chore(package): update lodash to version 4.17.2
https://greenkeeper.io/
2016-11-15 23:40:01 -08:00
Diwakar Cherukumilli 303851d86d Merge pull request #3054 from ParsePlatform/greenkeeper-lodash-4.17.1
Update lodash to version 4.17.1 🚀
2016-11-15 13:57:36 -06:00
greenkeeperio-bot 0cfae4d603 chore(package): update lodash to version 4.17.1
https://greenkeeper.io/
2016-11-14 23:21:44 -08:00
Florent Vilmart 7e0e48457b Basic test for CLI definitions (#3050)
* Add rudimentary test for cli definitions.
Use consistent import and quote style.

* nit: style

* Fix bug.
Add similar tests for live query definitions
2016-11-14 11:38:28 -05:00
steven-supersolid 7b5f89e037 Fix bug.
Add similar tests for live query definitions
2016-11-14 16:23:31 +00:00
steven-supersolid 7bc83c372d nit: style 2016-11-14 15:14:48 +00:00
steven-supersolid 28c1824394 Add rudimentary test for cli definitions.
Use consistent import and quote style.
2016-11-14 14:59:00 +00:00
jb 19271fa1ef Adding support for AfterFind (#2968) 2016-11-12 12:35:34 -05:00
Florent Vilmart b80d8a0d3d Fix #3038 (#3039) 2016-11-11 09:40:41 -08:00
Tyler Brock e9dfb68a37 Add support for maxTimeMS mongoOption (#3018) 2016-11-11 08:03:35 -08:00
Florent Vilmart fbbc23772b deploy to dockerHub on tags (#3011) 2016-11-11 10:27:01 -05:00
Steven Shipton 5082609937 Allow numberOrBooleanParser to parse string correctly. Improve error message in numberParser. Add env variable PARSE_SERVER_CLUSTER for cluster. Add tests (#3034) 2016-11-11 09:40:45 -05:00
Halim Qarroum 9abf17730f Injected the username when password has changed. (#3033)
* Injected the username when password has changed.

When the resetPassword controller has successfully updated the user's password, it now redirects the user to the password changed page with the username as a query parameters.

* Update unit test for password_reset_success redirect
2016-11-10 12:40:17 -05:00
Steven Shipton bb9ce773bb Don't create default folder when CLI parameter is 'null' (#3006) 2016-11-04 09:11:57 -04:00
Kirill Kravinsky 1029ddf563 Fix error in docs (#3007) 2016-11-04 09:11:13 -04:00
Kirill Kravinsky 71fb6cef5e Dockerizing Parse Server (#3005)
* Add Dockerfile

* Add Docker image docs

* Remove default PARSE_SERVER_CLOUD_CODE_MAIN setting + update docs

* Update docs

* Update documentation
2016-11-03 17:38:05 -04:00
Tyler Brock 2fb4f89324 Fix whitespace in getDatabaseAdapter 2016-11-03 10:53:37 -07:00
Greenkeeper 1ae3613608 chore(package): update request to version 2.78.0 (#3002)
https://greenkeeper.io/
2016-11-03 10:01:51 -04:00
Steven Shipton 5f335a9d30 Fix parameter name (#3001) 2016-11-03 10:01:31 -04:00
230 changed files with 23833 additions and 11122 deletions
+6 -1
View File
@@ -4,6 +4,11 @@
],
"presets": [
"es2015",
"stage-0"
"stage-3",
["env", {
"targets": {
"node": "4.6"
}
}]
]
}
+21
View File
@@ -0,0 +1,21 @@
node_modules
npm-debug.log
*.md
PATENTS
LICENSE
Dockerfile
.dockerignore
.gitignore
.travis.yml
.istanbul.yml
.git
.github
# Build folder
lib/
# Tests
spec/
# IDEs
.idea/
+3
View File
@@ -0,0 +1,3 @@
lib
coverage
+27
View File
@@ -0,0 +1,27 @@
{
"root": true,
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
},
"parser": "babel-eslint",
"plugins": [
"flowtype"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"rules": {
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
"no-trailing-spaces": 2,
"eol-last": 2,
"space-in-parens": ["error", "never"],
"no-multiple-empty-lines": 1,
"prefer-const": "error",
"space-infix-ops": "error",
"no-useless-escape": "off"
}
}
+9 -33
View File
@@ -1,50 +1,28 @@
Please read the following instructions carefully.
We use GitHub Issues for bugs.
Check out https://github.com/ParsePlatform/parse-server/issues/1271 for an ideal bug report.
The closer your issue report is to that one, the more likely we are to be able to help, and the more likely we will be to fix the issue quickly!
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
Many members of the community use Stack Overflow and Server Fault to ask questions.
Read through the existing questions or ask your own!
- Stack Overflow: http://stackoverflow.com/questions/tagged/parse.com
- Server Fault: https://serverfault.com/tags/parse
You may also search through existing issues before opening a new one: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue
For database migration help, please file a bug report at https://parse.com/help#report
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server!
- [ ] You've met the prerequisites: https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites.
- [ ] You're running the latest version of Parse Server: https://github.com/ParsePlatform/parse-server/releases
- [ ] You've searched through existing issues: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue Chances are that your issue has been reported or resolved before.
- [ ] You have filled out every section below. Issues without sufficient information are more likely to be closed.
--
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
### Issue Description
[DELETE EVERYTHING ABOVE THIS LINE BEFORE SUBMITTING YOUR ISSUE]
Describe your issue in as much detail as possible.
[FILL THIS OUT]
### Steps to reproduce
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
1. [FILL THIS OUT]
2. [FILL THIS OUT]
3. [FILL THIS OUT]
#### Expected Results
[FILL THIS OUT]
What you expected to happen.
#### Actual Outcome
[FILL THIS OUT]
What is happening instead.
### Environment Setup
@@ -62,6 +40,4 @@ Please include a detailed list of steps that reproduce the issue. Include curl c
### Logs/Trace
You can turn on additional logging by configuring VERBOSE=1 in your environment.
[FILL THIS OUT]
Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment.
+2
View File
@@ -13,6 +13,7 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
@@ -44,6 +45,7 @@ lib/
# cache folder
.cache
.eslintcache
# Mac DS_Store files
.DS_Store
-2
View File
@@ -1,2 +0,0 @@
instrumentation:
excludes: ["**/spec/**"]
+11
View File
@@ -0,0 +1,11 @@
{
"reporter": [
"lcov",
"text-summary"
],
"exclude": [
"**/spec/**",
"lib/"
]
}
+52 -43
View File
@@ -1,61 +1,70 @@
language: node_js
dist: trusty
sudo: required
node_js:
- '4.5'
- '6.1'
services:
- mongodb
- postgresql
- redis-server
- docker
addons:
postgresql: '9.5'
before_script:
- ls -al "$HOME/.mongodb/versions"
- 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
env:
global:
- COVERAGE_OPTION='./node_modules/.bin/istanbul cover'
matrix:
- MONGODB_VERSION=3.0.8
- MONGODB_VERSION=3.2.6
- PARSE_SERVER_TEST_DB=postgres
- PARSE_SERVER_TEST_CACHE=redis
matrix:
fast_finish: true
allow_failures:
- env: PARSE_SERVER_TEST_DB=postgres
apt_packages:
- postgresql-9.5-postgis-2.3
branches:
only:
- master
- /^[0-9]+.[0-9]+.[0-9]+(-.*)?$/
- /^greenkeeper/.*$/
cache:
directories:
- node_modules
- "$HOME/.mongodb/versions"
# Test stage
stage: test
env:
global:
- COVERAGE_OPTION='./node_modules/.bin/nyc'
- NODE_VERSION=6.10
matrix:
- MONGODB_VERSION=3.2.13
- MONGODB_VERSION=3.4.4
- PARSE_SERVER_TEST_DB=postgres
- PARSE_SERVER_TEST_CACHE=redis
- NODE_VERSION=8.1.3
before_install:
- nvm install $NODE_VERSION
- nvm use $NODE_VERSION
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
after_script:
- bash <(curl -s https://codecov.io/bash)
after_failure:
- (cd resources; python travis_after_all.py)
after_success:
- (cd resources; python travis_after_all.py)
- export $(cat resources/.to_export_back)
deploy:
- provider: script
skip_cleanup: true
script: ./resources/npm-git.sh
on:
branch: master
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
- provider: npm
email:
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
api_key:
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
on:
tags: true
all_branches: true
condition: "$MONGODB_VERSION = '3.0.8'"
repo: ParsePlatform/parse-server
jobs:
include:
# release on github latest branch
- stage: release
node_js: '4.6'
env:
before_script: skip
after_script: skip
script: skip
deploy:
- provider: script
skip_cleanup: true
script: ./resources/npm-git.sh
on:
branch: master
- 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
+411 -3
View File
@@ -1,17 +1,425 @@
## Parse Server Changelog
### 2.2.25-beta.1
### master
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.2...master)
### 2.6.2
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.1...2.6.2)
#### Improvements:
* PushWorker/PushQueue channels are properly prefixed with the Parse applicationId, thanks to [Marvel Mathew](https://github.com/marvelm)
* You can use Parse.Cloud.afterSave hooks on _PushStatus
* You can use Parse.Cloud.onLiveQueryEvent to track the number of clients and subscriptions
* Adds support for more fields from the Audience class.
#### New Features:
* Push: Adds ability to track sentPerUTC offset if your push scheduler supports it.
* Push: Adds support for cleaning up invalid deviceTokens from _Installation (PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS=1).
#### Dependency Updates:
* [ws@3.2.0](https://www.npmjs.com/package/ws)
* [pg-promise@6.5.3](https://www.npmjs.com/package/pg-promise)
* [winston-daily-rotate-file@1.5.0](https://www.npmjs.com/package/winston-daily-rotate-file)
* [body-parser@1.18.1](https://www.npmjs.com/package/body-parser)
##### Devevelopment Dependencies Updates:
* [nodemon@1.12.1](https://www.npmjs.com/package/nodemon)
* [mongodb-runner@3.6.0](https://www.npmjs.com/package/mongodb-runner)
* [babel-eslint@8.0.0](https://www.npmjs.com/package/babel-eslint)
### 2.6.1
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.0...2.6.1)
#### Improvements:
* Improves overall performance of the server, more particularly with large query results.
* Improves performance of InMemoryCacheAdapter by removing serialization.
* Improves logging performance by skipping necessary log calls.
* Refactors object routers to simplify logic.
* Adds automatic indexing on $text indexes, thanks to [Diamon Lewis](https://github.com/dplewis)
#### New Features:
* Push: Adds ability to send localized pushes according to the _Installation localeIdentifier
* Push: proper support for scheduling push in user's locale time, thanks to [Marvel Mathew](https://github.com/marvelm)
* LiveQuery: Adds ability to use LiveQuery with a masterKey, thanks to [Jeremy May](https://github.com/kenishi)
#### Bug Fixes:
* Fixes an issue that would duplicate Session objects per userId-installationId pair.
* Fixes an issue affecting pointer permissions introduced in this release.
* Fixes an issue that would prevent displaying audiences correctly in dashboard.
* Fixes an issue affecting preventLoginWithUnverifiedEmail upon signups.
#### Dependency Updates:
* [pg-promise@6.3.2](https://www.npmjs.com/package/pg-promise)
* [body-parser@1.18.0](https://www.npmjs.com/package/body-parser)
* [nodemon@1.11.1](https://www.npmjs.com/package/nodemon)
##### Devevelopment Dependencies Updates:
* [babel-cli@6.26.0](https://www.npmjs.com/package/babel-cli)
### 2.6.0
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.3...2.6.0)
#### Breaking Changes:
* [parse-server-s3-adapter@1.2.0](https://www.npmjs.com/package/parse-server-s3-adapter): A new deprecation notice is introduced with parse-server-s3-adapter's version 1.2.0. An upcoming release will remove passing key and password arguments. AWS credentials should be set using AWS best practices. See the [Deprecation Notice for AWS credentials]( https://github.com/parse-server-modules/parse-server-s3-adapter/blob/master/README.md#deprecation-notice----aws-credentials) section of the adapter's README.
#### New Features
* Polygon is fully supported as a type, thanks to [Diamond Lewis](https://github.com/dplewis)
* Query supports PolygonContains, thanks to [Diamond Lewis](https://github.com/dplewis)
#### Improvements
* Postgres: Adds support nested contains and containedIn, thanks to [Diamond Lewis](https://github.com/dplewis)
* Postgres: Adds support for `null` in containsAll queries, thanks to [Diamond Lewis](https://github.com/dplewis)
* Cloud Code: Request headers are passed to the cloud functions, thanks to [miguel-s](https://github.com/miguel-s)
* Push: All push queries now filter only where deviceToken exists
#### Bug Fixes:
* Fixes issue affecting updates of _User objects when authData was passed.
* Push: Pushing to an empty audience should now properly report a failed _PushStatus
* Linking Users: Fixes issue affecting linking users with sessionToken only
#### Dependency Updates:
* [ws@3.1.0](https://www.npmjs.com/package/ws)
* [mime@1.4.0](https://www.npmjs.com/package/mime)
* [semver@5.4.0](https://www.npmjs.com/package/semver)
* [uws@8.14.1](https://www.npmjs.com/package/uws)
* [bcrypt@1.0.3](https://www.npmjs.com/package/bcrypt)
* [mongodb@2.2.31](https://www.npmjs.com/package/mongodb)
* [redis@2.8.0](https://www.npmjs.com/package/redis)
* [pg-promise@6.3.1](https://www.npmjs.com/package/pg-promise)
* [commander@2.11.0](https://www.npmjs.com/package/commander)
##### Devevelopment Dependencies Updates:
* [jasmine@2.8.0](https://www.npmjs.com/package/jasmine)
* [babel-register@6.26.0](https://www.npmjs.com/package/babel-register)
* [babel-core@6.26.0](https://www.npmjs.com/package/babel-core)
* [cross-env@5.0.2](https://www.npmjs.com/package/cross-env)
### 2.5.3
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.2...2.5.3)
#### New Features:
* badge property on android installations will now be set as on iOS (#3970), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Bug Fixes:
* Fixes incorrect number parser for cache options
### 2.5.2
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.1...2.5.2)
#### Improvements:
* Restores ability to run on node >= 4.6
* Adds ability to configure cache from CLI
* Removes runtime check for node >= 4.6
### 2.5.1
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.0...2.5.1)
#### New Features:
* Adds ability to set default objectId size (#3950), thanks to [Steven Shipton](https://github.com/steven-supersolid)
#### Improvements:
* Uses LRU cache instead of InMemoryCache by default (#3979), thanks to [Florent Vilmart](https://github.com/flovilmart)
* iOS pushes are now using HTTP/2.0 instead of binary API (#3983), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Dependency Updates:
* [parse@1.10.0](https://www.npmjs.com/package/parse)
* [pg-promise@6.3.0](https://www.npmjs.com/package/pg-promise)
* [parse-server-s3-adapter@1.1.0](https://www.npmjs.com/package/parse-server-s3-adapter)
* [parse-server-push-adapter@2.0.0](https://www.npmjs.com/package/parse-server-push-adapter)
### 2.5.0
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.4.2...2.5.0)
#### New Features:
* Adds ability to run full text search (#3904), thanks to [Diamond Lewis](https://github.com/dplewis)
* Adds ability to run `$withinPolygon` queries (#3889), thanks to [Diamond Lewis](https://github.com/dplewis)
* Adds ability to pass read preference per query with mongodb (#3865), thanks to [davimacedo](https://github.com/davimacedo)
* beforeFind trigger now includes `isGet` for get queries (#3862), thanks to [davimacedo](https://github.com/davimacedo)
* Adds endpoints for dashboard's audience API (#3861), thanks to [davimacedo](https://github.com/davimacedo)
* Restores the job scheduling endpoints (#3927), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Improvements:
* Removes unnecessary warning when using maxTimeMs with mongodb, thanks to [Tyler Brock](https://github.com/tbrock)
* Improves access control on system classes (#3916), thanks to [Worathiti Manosroi](https://github.com/pungme)
* Adds bytes support in postgres (#3894), thanks to [Diamond Lewis](https://github.com/dplewis)
#### Bug Fixes:
* Fixes issue with vkontakte adapter that would hang the request, thanks to [Denis Trofimov](https://github.com/denistrofimov)
* Fixes issue affecting null relational data (#3924), thanks to [davimacedo](https://github.com/davimacedo)
* Fixes issue affecting session token deletion (#3937), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fixes issue affecting the serverInfo endpoint (#3933), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fixes issue affecting beforeSave with dot-noted sub-documents (#3912), thanks to [IlyaDiallo](https://github.com/IlyaDiallo)
* Fixes issue affecting emails being sent when using a 3rd party auth (#3882), thanks to [davimacedo](https://github.com/davimacedo)
#### Dependency Updates:
* [commander@2.10.0](https://www.npmjs.com/package/commander)
* [pg-promise@5.9.7](https://www.npmjs.com/package/pg-promise)
* [lru-cache@4.1.0](https://www.npmjs.com/package/lru-cache)
* [mongodb@2.2.28](https://www.npmjs.com/package/mongodb)
##### Devevelopment dependencies
* [babel-core@6.25.0](https://www.npmjs.com/package/babel-core)
* [cross-env@5.0.1](https://www.npmjs.com/package/cross-env)
* [nyc@11.0.2](https://www.npmjs.com/package/nyc)
### 2.4.2
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.4.1...2.4.2)
#### New Features:
* ParseQuery: Support for withinPolygon [#3866](https://github.com/parse-community/parse-server/pull/3866), thanks to [Diamond Lewis](https://github.com/dplewis)
#### Improvements:
* Postgres: Use transactions when deleting a class, [#3869](https://github.com/parse-community/parse-server/pull/3836), thanks to [Vitaly Tomilov](https://github.com/vitaly-t)
* Postgres: Proper support for GeoPoint equality query, [#3874](https://github.com/parse-community/parse-server/pull/3836), thanks to [Diamond Lewis](https://github.com/dplewis)
* beforeSave and liveQuery will be correctly triggered on email verification [#3851](https://github.com/parse-community/parse-server/pull/3851), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Bug fixes:
* Skip authData validation if it hasn't changed, on PUT requests [#3872](https://github.com/parse-community/parse-server/pull/3872), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Dependency Updates:
* [mongodb@2.2.27](https://www.npmjs.com/package/mongodb)
* [pg-promise@5.7.2](https://www.npmjs.com/package/pg-promise)
### 2.4.1
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.4.0...2.4.1)
#### Bug fixes:
* Fixes issue affecting relation updates ([#3835](https://github.com/parse-community/parse-server/pull/3835), [#3836](https://github.com/parse-community/parse-server/pull/3836)), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fixes issue affecting sending push notifications, thanks to [Felipe Andrade](https://github.com/felipemobile)
* Session are always cleared when updating the passwords ([#3289](https://github.com/parse-community/parse-server/pull/3289), [#3821](https://github.com/parse-community/parse-server/pull/3821), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Dependency Updates:
* [body-parser@1.17.2](https://www.npmjs.com/package/body-parser)
* [pg-promise@5.7.1](https://www.npmjs.com/package/pg-promise)
* [ws@3.0.0](https://www.npmjs.com/package/ws)
### 2.4.0
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.8...2.4.0)
Starting 2.4.0, parse-server is tested against node 6.10 and 7.10, mongodb 3.2 and 3.4.
If you experience issues with older versions, please [open a issue](https://github.com/parse-community/parse-server/issues).
#### New Features:
* Adds `count` Class Level Permission ([#3814](https://github.com/parse-community/parse-server/pull/3814)), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Proper graceful shutdown support ([#3786](https://github.com/parse-community/parse-server/pull/3786)), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Let parse-server store as `scheduled` Push Notifications with push_time (#3717, #3722), thanks to [Felipe Andrade](https://github.com/felipemobile)
#### Improvements
* Parse-Server images are built through docker hub, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Skip authData validation if it hasn't changed, thanks to [Florent Vilmart](https://github.com/flovilmart)
* [postgres] Improve performance when adding many new fields to the Schema ([#3740](https://github.com/parse-community/parse-server/pull/3740)), thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
* Test maintenance, wordsmithing and nits ([#3744](https://github.com/parse-community/parse-server/pull/3744)), thanks to [Arthur Cinader](https://github.com/acinader)
#### Bug Fixes:
* [postgres] Fixes issue affecting deleting multiple fields of a Schema ([#3734](https://github.com/parse-community/parse-server/pull/3734), [#3735](https://github.com/parse-community/parse-server/pull/3735)), thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
* Fix issue affecting _PushStatus state ([#3808](https://github.com/parse-community/parse-server/pull/3808)), thanks to [Florent Vilmart](https://github.com/flovilmart)
* requiresAuthentication Class Level Permission behaves correctly, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Email Verification related fields are not exposed ([#3681](https://github.com/parse-community/parse-server/pull/3681), [#3393](https://github.com/parse-community/parse-server/pull/3393), [#3432](https://github.com/parse-community/parse-server/pull/3432)), thanks to [Anthony Mosca](https://github.com/aontas)
* HTTP query parameters are properly obfuscated in logs ([#3793](https://github.com/parse-community/parse-server/pull/3793), [#3789](https://github.com/parse-community/parse-server/pull/3789)), thanks to [@youngerong](https://github.com/youngerong)
* Improve handling of `$near` operators in `$or` queries ([#3767](https://github.com/parse-community/parse-server/pull/3767), [#3798](https://github.com/parse-community/parse-server/pull/3798)), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
* Fix issue affecting arrays of pointers ([#3169](https://github.com/parse-community/parse-server/pull/3169)), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fix issue affecting overloaded query constraints ([#3723](https://github.com/parse-community/parse-server/pull/3723), [#3678](https://github.com/parse-community/parse-server/pull/3678)), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Properly catch unhandled rejections in _Installation updates ([#3795](https://github.com/parse-community/parse-server/pull/3795)), thanks to [kahoona77](https://github.com/kahoona77)
#### Dependency Updates:
* [uws@0.14.5](https://www.npmjs.com/package/uws)
* [mime@1.3.6](https://www.npmjs.com/package/mime)
* [mongodb@2.2.26](https://www.npmjs.com/package/mongodb)
* [pg-promise@5.7.0](https://www.npmjs.com/package/pg-promise)
* [semver@5.3.0](https://www.npmjs.com/package/semver)
##### Devevelopment dependencies
* [babel-cli@6.24.1](https://www.npmjs.com/package/babel-cli)
* [babel-core@6.24.1](https://www.npmjs.com/package/babel-core)
* [babel-preset-es2015@6.24.1](https://www.npmjs.com/package/babel-preset-es2015)
* [babel-preset-stage-0@6.24.1](https://www.npmjs.com/package/babel-preset-stage-0)
* [babel-register@6.24.1](https://www.npmjs.com/package/babel-register)
* [cross-env@5.0.0](https://www.npmjs.com/package/cross-env)
* [deep-diff@0.3.8](https://www.npmjs.com/package/deep-diff)
* [gaze@1.1.2](https://www.npmjs.com/package/gaze)
* [jasmine@2.6.0](https://www.npmjs.com/package/jasmine)
* [jasmine-spec-reporter@4.1.0](https://www.npmjs.com/package/jasmine-spec-reporter)
* [mongodb-runner@3.5.0](https://www.npmjs.com/package/mongodb-runner)
* [nyc@10.3.2](https://www.npmjs.com/package/nyc)
* [request-promise@4.2.1](https://www.npmjs.com/package/request-promise)
### 2.3.8
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.7...2.3.8)
#### New Features
* Support for PG-Promise options, thanks to [ren dong](https://github.com/rendongsc)
#### Improvements
* Improves support for graceful shutdown, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Improves configuration validation for Twitter Authentication, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
#### Bug Fixes
* Fixes issue affecting GeoPoint __type with Postgres, thanks to [zhoul-HS](https://github.com/zhoul-HS)
* Prevent user creation if username or password is empty, thanks to [Wissam Abirached](https://github.com/wabirached)
#### Dependency Updates:
* [cross-env@4.0.0 ](https://www.npmjs.com/package/cross-env)
* [ws@2.2.3](https://www.npmjs.com/package/ws)
* [babel-core@6.24.0](https://www.npmjs.com/package/babel-core)
* [uws@0.14.0](https://www.npmjs.com/package/uws)
* [babel-preset-es2015@6.24.0](https://www.npmjs.com/package/babel-preset-es2015)
* [babel-plugin-syntax-flow@6.18.0](https://www.npmjs.com/package/babel-plugin-syntax-flow)
* [babel-cli@6.24.0](https://www.npmjs.com/package/babel-cli)
* [babel-register@6.24.0](https://www.npmjs.com/package/babel-register)
* [winston-daily-rotate-file@1.4.6](https://www.npmjs.com/package/winston-daily-rotate-file)
* [mongodb@2.2.25](https://www.npmjs.com/package/mongodb)
* [redis@2.7.0](https://www.npmjs.com/package/redis)
* [pg-promise@5.6.4](https://www.npmjs.com/package/pg-promise)
* [parse-server-push-adapter@1.3.0](https://www.npmjs.com/package/parse-server-push-adapter)
### 2.3.7
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.6...2.3.7)
#### New Features
* New endpoint to resend verification email, thanks to [Xy Ziemba](https://github.com/xyziemba)
#### Improvements
* Add TTL option for Redis Cache Adapter, thanks to [Ryan Foster](https://github.com/f0ster)
* Update Postgres Storage Adapter, thanks to [Vitaly Tomilov](https://github.com/vitaly-t)
#### Bug Fixes
* Add index on Role.name, fixes (#3579), thanks to [Natan Rolnik](https://github.com/natanrolnik)
* Fix default value of userSensitiveFields, fixes (#3593), thanks to [Arthur Cinader](https://github.com/acinader)
#### Dependency Updates:
* [body-parser@1.17.1](https://www.npmjs.com/package/body-parser)
* [express@4.15.2](https://www.npmjs.com/package/express)
* [request@2.81.0](https://www.npmjs.com/package/request)
* [winston-daily-rotate-file@1.4.5](https://www.npmjs.com/package/winston-daily-rotate-file)
* [ws@2.2.0](https://www.npmjs.com/package/ws)
### 2.3.6
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.5...2.3.6)
#### Improvements
* Adds support for injecting a middleware for instumentation in the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Alleviate mongodb bug with $or queries [SERVER-13732](https://jira.mongodb.org/browse/SERVER-13732), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
#### Bug Fixes
* Fix issue affecting password policy and empty passwords, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
* Fix issue when logging url in non string objects, thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
#### Dependencies updates:
* [ws@2.1.0](https://npmjs.com/package/ws)
* [uws@0.13.0](https://npmjs.com/package/uws)
* [pg-promise@5.6.2](https://npmjs.com/package/pg-promise)
### 2.3.5
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.5)
#### Bug Fixes
* Allow empty client key
(#3497), thanks to [Arthur Cinader](https://github.com/acinader)
* Fix LiveQuery unsafe user
(#3525), thanks to [David Starke](https://github.com/dstarke)
* Use `flushdb` instead of `flushall` in RedisCacheAdapter
(#3523), thanks to [Jeremy Louie](https://github.com/JeremyPlease)
* Fix saving GeoPoints and Files in `_GlobalConfig` (Make sure we don't treat
dot notation keys as topLevel atoms)
(#3531), thanks to [Florent Vilmart](https://github.com/flovilmart)
### 2.3.3
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.2...2.3.3)
#### Breaking Changes
* **Minimum Node engine bumped to 4.6** (#3480), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Bug Fixes
* Add logging on failure to create file (#3424), thanks to [Arthur Cinader](https://github.com/acinader)
* Log Parse Errors so they are intelligible (#3431), thanks to [Arthur Cinader](https://github.com/acinader)
* MongoDB $or Queries avoid SERVER-13732 bug (#3476), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
* Mongo object to Parse object date serialization - avoid re-serialization of iso of type Date (#3389), thanks to [nodechefMatt](https://github.com/nodechefMatt)
#### Improvements
* Ground preparations for push scalability (#3080), thanks to [Florent Vilmart](https://github.com/flovilmart)
* Use uWS as optional dependency for ws server (#3231), thanks to [Florent Vilmart](https://github.com/flovilmart)
### 2.3.2
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.1...2.3.2)
#### New features
* Add parseFrameURL for masking user-facing pages (#3267), thanks to [Lenart Rudel](https://github.com/lenart)
#### Bug fixes
* Fix Parse-Server to work with winston-daily-rotate-1.4.2 (#3335), thanks to [Arthur Cinader](https://github.com/acinader)
#### Improvements
* Add support for regex string for password policy validatorPattern setting (#3331), thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
* LiveQuery should match subobjects with dot notation (#3322), thanks to [David Starke](https://github.com/dstarke)
* Reduce time to process high number of installations for push (#3264), thanks to [jeacott1](https://github.com/jeacott1)
* Fix trivial typo in error message (#3238), thanks to [Arthur Cinader](https://github.com/acinader)
### 2.3.1
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.0...2.3.1)
A major issue was introduced when refactoring the authentication modules.
This release addresses only that issue.
### 2.3.0
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.2.25...2.3.0)
#### Breaking changes
* Parse.Cloud.useMasterKey() is a no-op, please refer to (Cloud Code migration guide)[https://github.com/ParsePlatform/parse-server/wiki/Compatibility-with-Hosted-Parse#cloud-code]
* Authentication helpers are now proper adapters, deprecates oauth option in favor of auth.
* DEPRECATES: facebookAppIds, use `auth: { facebook: { appIds: ["AAAAAAAAA" ] } }`
* `email` field is not returned anymore for `Parse.User` queries. (Provided only on the user itself if provided).
#### New Features
* Adds ability to restrict access through Class Level Permissions to only authenticated users [see docs](http://parseplatform.github.io/docs/ios/guide/#requires-authentication-permission-requires-parse-server---230)
* Adds ability to strip sensitive data from `_User` responses, strips emails by default, thanks to [Arthur Cinader](https://github.com/acinader)
* Adds password history support for password policies, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
#### Improvements
* Bump parse-server-s3-adapter to 1.0.6, thanks to [Arthur Cinader](https://github.com/acinader)
* Using PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS let you create user sessions when passing {installationId: "xxx-xxx"} on signup in cloud code, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Add CLI option to pass `host` parameter when creating parse-server from CLI, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
#### Bug fixes
* Ensure batch routes are only using posix paths, thanks to [Steven Shipton](https://github.com/steven-supersolid)
* Ensure falsy options from CLI are properly taken into account, thanks to [Steven Shipton](https://github.com/steven-supersolid)
* Fixes issues affecting calls to `matchesKeyInQuery` with pointers.
* Ensure that `select` keys can be changed in triggers (beforeFind...), thanks to [Arthur Cinader](https://github.com/acinader)
#### Housekeeping
* Enables and enforces linting with eslint, thanks to [Arthur Cinader](https://github.com/acinader)
### 2.2.25
Postgres support requires v9.5
#### New Features
* Dockerizing Parse Server, thanks to [Kirill Kravinsky](https://github.com/woyorus)
* Login with qq, wechat, weibo, thanks to [haifeizhang]()
* Password policy, validation and expiration, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
* Health check on /health, thanks to [Kirill Kravinsky](https://github.com/woyorus)
* Reuse SchemaCache across requests option, thanks to [Steven Shipton](https://github.com/steven-supersolid)
#### Improvements
* Better support for CLI options, thanks to [Steven Shipton](https://github.com/steven-supersolid)
* Specity a database timeout with maxTimeMS, thanks to [Tyler Brock](https://github.com/TylerBrock)
* Adds the username to reset password success pages, thanks to [Halim Qarroum](https://github.com/HQarroum)
* Better support for Redis cache adapter, thanks to [Tyler Brock](https://github.com/TylerBrock)
* Better coverage of Postgres, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
#### Bug Fixes
* Fixes issue when sending push to multiple installations, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fixes issues with twitter authentication, thanks to [jonas-db](https://github.com/jonas-db)
* Ignore createdAt fields update, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
* Improve support for array equality with LiveQuery, thanks to [David Poetzsch-Heffter](https://github.com/dpoetzsch)
* Improve support for batch endpoint when serverURL and publicServerURL have different paths, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Support saving relation objects, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
### 2.2.24
#### New Features
@@ -22,7 +430,7 @@ Postgres support requires v9.5
* Better Postgres support, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
* Logs the function name when failing (#2963), thanks to [Michael Helvey](https://github.com/michaelhelvey)
* CLI: forces closing the connections with SIGINT/SIGTERM (#2964), thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
* Reduce the number of calls to the _SCHEMA table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
* Reduce the number of calls to the `_SCHEMA` table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
* LiveQuery: Support for Role ACL's, thanks to [Aaron Blondeau](https://github.com/aaron-blondeau-dose)
#### Bug Fixes
@@ -69,7 +477,7 @@ Postgres support requires v9.5
### 2.2.20
* New: Adds CloudCode handler for `beforeFind`, thanks to [Florent Vilmart](https://github.com/flovilmart)
* New: RedisCacheAdapter for syncing schema, role and user caches across servers, thanks to [Florent Vilmart](https://github.com/flovilmart)
* New: RedisCacheAdapter for syncing schema, role and user caches across servers, thanks to [Florent Vilmart](https://github.com/flovilmart)
* New: Latest master build available at `ParsePlatform/parse-server#latest`, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fix: Better support for upgradeToRevocableSession with missing session token, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fix: Removes babel-polyfill runtime dependency, thanks to [Florent Vilmart](https://github.com/flovilmart)
+46
View File
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## 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.
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.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+14 -1
View File
@@ -9,7 +9,20 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
* Take testing seriously! Aim to increase the test coverage with every pull request.
* Run the tests for the file you are working on with `npm test spec/MyFile.spec.js`
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
* Lint your code by running `npm run lint` to make sure all your code is not gonna be rejected by the CI.
* Never publish the lib folder.
##### 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)
* 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
This project adheres to the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/#Parse Server/fjm@fb.com). By participating, you are expected to honor this code.
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.
+21
View File
@@ -0,0 +1,21 @@
FROM node:boron
RUN mkdir -p /parse-server
COPY ./ /parse-server/
RUN mkdir -p /parse-server/config
VOLUME /parse-server/config
RUN mkdir -p /parse-server/cloud
VOLUME /parse-server/cloud
WORKDIR /parse-server
RUN npm install && \
npm run build
ENV PORT=1337
EXPOSE $PORT
ENTRYPOINT ["npm", "start", "--"]
+4
View File
@@ -28,3 +28,7 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----
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.
+4
View File
@@ -31,3 +31,7 @@ necessarily infringed by the Software standing alone.
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
or contributory infringement or inducement to infringe any patent, including a
cross-claim or counterclaim.
-----
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.
+59 -25
View File
@@ -1,7 +1,7 @@
![Parse Server logo](.github/parse-server-logo.png?raw=true)
[![Build Status](https://img.shields.io/travis/ParsePlatform/parse-server/master.svg?style=flat)](https://travis-ci.org/ParsePlatform/parse-server)
[![Coverage Status](https://img.shields.io/codecov/c/github/ParsePlatform/parse-server/master.svg)](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
[![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)
@@ -12,17 +12,30 @@ Parse Server works with the Express web application framework. It can be added t
# Getting Started
[![Greenkeeper badge](https://badges.greenkeeper.io/parse-community/parse-server.svg)](https://greenkeeper.io/)
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parse.com/learn/parse-server-video-series-april-2016/](http://blog.parse.com/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 locally
## Running Parse Server
### Locally
```
$ npm install -g parse-server mongodb-runner
$ 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
```
$ 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.
@@ -32,7 +45,7 @@ That's it! You are now running a standalone version of Parse Server on your mach
### Saving your first object
Now that you're running Parse Server, it is time to save your first object. We'll use the [REST API](https://parse.com/docs/rest/guide), but you can easily do the same using any of the [Parse SDKs](https://parseplatform.github.io/#sdks). Run the following:
Now that you're running Parse Server, it is time to save your first object. We'll use the [REST API](http://docs.parseplatform.org/rest/guide), but you can easily do the same using any of the [Parse SDKs](http://parseplatform.org/#sdks). Run the following:
```bash
curl -X POST \
@@ -94,19 +107,19 @@ $ curl -X GET \
```
To learn more about using saving and querying objects on Parse Server, check out the [Parse documentation](https://parse.com/docs).
To learn more about using saving and querying objects on Parse Server, check out the [Parse documentation](http://docs.parseplatform.org).
### 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/ParsePlatform/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://github.com/parse-community/parse-server/wiki/Parse-Server-Guide#using-parse-sdks-with-parse-server).
## Running Parse Server elsewhere
Once you have a better understanding of how the project works, please refer to the [Parse Server wiki](https://github.com/ParsePlatform/parse-server/wiki) for in-depth guides to deploy Parse Server to major infrastructure providers. Read on to learn more about additional ways of running Parse Server.
Once you have a better understanding of how the project works, please refer to the [Parse Server wiki](https://github.com/parse-community/parse-server/wiki) for in-depth guides to deploy Parse Server to major infrastructure providers. Read on to learn more about additional ways of running Parse Server.
### Parse Server Sample Application
We have provided a basic [Node.js application](https://github.com/ParsePlatform/parse-server-example) that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:
We have provided a basic [Node.js application](https://github.com/parse-community/parse-server-example) that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:
* [Heroku and mLab](https://devcenter.heroku.com/articles/deploying-a-parse-server-to-heroku)
* [AWS and Elastic Beanstalk](http://mobile.awsblog.com/post/TxCD57GZLM2JR/How-to-set-up-Parse-Server-on-AWS-using-AWS-Elastic-Beanstalk)
@@ -114,10 +127,10 @@ We have provided a basic [Node.js application](https://github.com/ParsePlatform/
* [Microsoft Azure](https://azure.microsoft.com/en-us/blog/azure-welcomes-parse-developers/)
* [SashiDo](https://blog.sashido.io/tag/migration/)
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
* [NodeChef](https://nodechef.com/blog/post/6/migrate-from-parse-to-nodechef%E2%80%99s-managed-parse-server)
* [Pivotal Web Services](https://github.com/cf-platform-eng/pws-parse-server)
* [Back4app](http://blog.back4app.com/2016/03/01/quick-wizard-migration/)
* [HyperDev](https://hyperdev.com/blog/use-parse-server-apps-backend-hyperdev/)
* [Gomix](https://gomix.com/#!/project/parse-server)
* [Flynn](https://flynn.io/blog/parse-apps-on-flynn)
### Parse Server + Express
@@ -149,7 +162,7 @@ For a full list of available options, run `parse-server --help`.
## Logging
Parse Server will, by default, will log:
Parse Server will, by default, log:
* to the console
* daily rotating files as new line delimited JSON
@@ -159,15 +172,13 @@ Logs are also be viewable in Parse Dashboard.
**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/ParsePlatform/parse-server/wiki). The [Parse Server guide](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide) is a good place to get started. If you're interested in developing for Parse Server, the [Development guide](https://github.com/ParsePlatform/parse-server/wiki/Development-Guide) will help you get set up.
## Migrating an Existing Parse App
The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. There are a few areas where Parse Server does not provide compatibility with the hosted version of Parse. Learn more in the [Migration guide](https://parse.com/migration).
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.
## Configuration
@@ -183,7 +194,7 @@ For the full list of available options, run `parse-server --help`.
* `port` - The default port is 1337, specify this parameter to use a different port.
* `serverURL` - URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code.
* `cloud` - The absolute path to your cloud code `main.js` file.
* `push` - Configuration options for APNS and GCM push. See the [Push Notifications wiki entry](https://github.com/ParsePlatform/parse-server/wiki/Push).
* `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
@@ -199,15 +210,20 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
* `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.
* `oauth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/OAuth).
* `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/ParsePlatform/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
* `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/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
* `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).
* `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.
##### Logging
@@ -269,6 +285,20 @@ var server = ParseServer({
duration: 5, // duration policy setting determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000.
threshold: 3, // threshold policy setting determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000.
},
// optional settings to enforce password policies
passwordPolicy: {
// 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
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) },
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.
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
}
});
```
@@ -313,19 +343,19 @@ Parse Server allows developers to choose from several options when hosting files
* `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 wiki](https://github.com/ParsePlatform/parse-server/wiki/Configuring-File-Adapters).
`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).
# 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.
If you believe you've found an issue with Parse Server, make sure these boxes are checked before [reporting an issue](https://github.com/ParsePlatform/parse-server/issues):
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):
- [ ] You've met the [prerequisites](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites).
- [ ] You've met the [prerequisites](http://docs.parseplatform.org/parse-server/guide/#prerequisites).
- [ ] You're running the [latest version](https://github.com/ParsePlatform/parse-server/releases) of Parse Server.
- [ ] You're running the [latest version](https://github.com/parse-community/parse-server/releases) of Parse Server.
- [ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
- [ ] You've searched through [existing issues](https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
# Want to ride the bleeding edge?
@@ -342,3 +372,7 @@ npm install parseplatform/parse-server.git#latest
# 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).
-----
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
@@ -95,10 +95,10 @@ confirm 'Y' 'Do you want to continue? (Y/n): '
check_node
check_npm
printf "Setting up parse-server in %s" "${INSTALL_DIR}\n"
printf "Setting up parse-server in %s\n" "${INSTALL_DIR}"
if [ -d "${INSTALL_DIR}" ]; then
echo "{CHECK} ${INSTALL_DIR} exists"
echo "${CHECK} ${INSTALL_DIR} exists"
else
mkdir -p "${INSTALL_DIR}"
echo "${CHECK} Created ${INSTALL_DIR}"
+52 -44
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "2.2.25-beta.1",
"version": "2.6.2",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -18,67 +18,75 @@
],
"license": "BSD-3-Clause",
"dependencies": {
"bcryptjs": "2.3.0",
"body-parser": "1.15.2",
"commander": "2.9.0",
"bcryptjs": "2.4.3",
"body-parser": "1.18.1",
"commander": "2.11.0",
"deepcopy": "0.6.3",
"express": "4.14.0",
"express": "4.15.3",
"intersect": "1.0.1",
"lodash": "4.16.5",
"lru-cache": "4.0.1",
"mime": "1.3.4",
"mongodb": "2.2.10",
"multer": "1.2.0",
"parse": "1.9.2",
"lodash": "4.17.4",
"lru-cache": "4.1.1",
"mime": "1.4.0",
"mongodb": "2.2.31",
"multer": "1.3.0",
"parse": "1.10.0",
"parse-server-fs-adapter": "1.0.1",
"parse-server-push-adapter": "1.1.0",
"parse-server-s3-adapter": "1.0.5",
"parse-server-push-adapter": "2.0.0",
"parse-server-s3-adapter": "1.2.0",
"parse-server-simple-mailgun-adapter": "1.0.0",
"pg-promise": "5.3.5",
"redis": "2.6.3",
"request": "2.76.0",
"semver": "5.2.0",
"tv4": "1.2.7",
"winston": "2.3.0",
"winston-daily-rotate-file": "1.3.1",
"ws": "1.1.1"
"pg-promise": "6.5.3",
"redis": "2.8.0",
"request": "2.81.0",
"semver": "5.4.0",
"tv4": "1.3.0",
"uuid": "^3.1.0",
"winston": "2.3.1",
"winston-daily-rotate-file": "1.5.0",
"ws": "3.2.0"
},
"devDependencies": {
"babel-cli": "6.18.0",
"babel-core": "6.18.2",
"babel-plugin-syntax-flow": "6.13.0",
"babel-plugin-transform-flow-strip-types": "6.18.0",
"babel-preset-es2015": "6.14.0",
"babel-preset-stage-0": "6.5.0",
"babel-register": "6.18.0",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-eslint": "^8.0.0",
"babel-plugin-syntax-flow": "6.18.0",
"babel-plugin-transform-flow-strip-types": "6.22.0",
"babel-preset-env": "1.6.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-stage-3": "6.24.1",
"babel-register": "6.26.0",
"bcrypt-nodejs": "0.0.3",
"cross-env": "3.1.3",
"deep-diff": "0.3.4",
"gaze": "1.1.1",
"istanbul": "1.0.0-alpha.1",
"jasmine": "2.5.2",
"jasmine-spec-reporter": "^2.7.0",
"mongodb-runner": "3.4.0",
"nodemon": "1.11.0",
"request-promise": "4.1.1"
"cross-env": "5.0.2",
"deep-diff": "0.3.8",
"eslint": "^4.4.1",
"eslint-plugin-flowtype": "^2.25.0",
"gaze": "1.1.2",
"jasmine": "2.8.0",
"jasmine-spec-reporter": "^4.1.0",
"mongodb-runner": "3.6.0",
"nodemon": "1.12.1",
"nyc": "^11.0.2",
"request-promise": "4.2.1"
},
"scripts": {
"dev": "npm run build && node bin/dev",
"build": "babel src/ -d lib/",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 node $COVERAGE_OPTION ./spec/support/runner.js",
"test:win": "npm run pretest && cross-env NODE_ENV=test TESTING=1 node ./node_modules/jasmine/bin/jasmine.js && npm run posttest",
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/istanbul cover' npm test",
"coverage:win": "npm run pretest && cross-env NODE_ENV=test TESTING=1 node ./node_modules/babel-istanbul/lib/cli.js cover ./node_modules/jasmine/bin/jasmine.js && npm run posttest",
"lint": "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 $COVERAGE_OPTION jasmine",
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/nyc' npm test",
"coverage:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 node ./node_modules/.bin/nyc ./node_modules/jasmine/bin/jasmine.js",
"start": "node ./bin/parse-server",
"prepublish": "npm run build"
},
"engines": {
"node": ">=4.5"
"node": ">=4.6"
},
"bin": {
"parse-server": "./bin/parse-server"
},
"optionalDependencies": {
"bcrypt": "0.8.7"
"bcrypt": "1.0.3",
"uws": "^8.14.1"
}
}
+2
View File
@@ -35,6 +35,8 @@
padding: 0 0 0 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Invalid Link</h1>
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<!-- This page is displayed when someone navigates to a verify email or reset password link
but their security token is wrong. This can either mean the user has clicked on a
stale link (i.e. re-click on a password reset link after resetting their password) or
(rarely) this could be a sign of a malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Invalid Link</title>
<style type='text/css'>
.container {
border-width: 0px;
display: block;
font: inherit;
font-family: 'Helvetica Neue', Helvetica;
font-size: 16px;
height: 30px;
line-height: 16px;
margin: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
position: relative;
vertical-align: baseline;
}
h1, h2, h3, h4, h5 {
color: #0067AB;
display: block;
font: inherit;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 0 0 15px 0;
padding: 0 0 0 0;
}
</style>
</head>
<script type="text/javascript">
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
window.onload = addDataToForm;
function addDataToForm() {
var username = getUrlParameter("username");
document.getElementById("usernameField").value = username;
var appId = getUrlParameter("appId");
document.getElementById("resendForm").action = '/apps/' + appId + '/resend_verification_email'
}
</script>
<body>
<div class="container">
<h1>Invalid Verification Link</h1>
<form id="resendForm" method="POST" action="/resend_verification_email">
<input id="usernameField" class="form-control" name="username" type="hidden" value="">
<button type="submit" class="btn btn-default">Resend Link</button>
</form>
</div>
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<!-- This page is displayed when someone navigates to a verify email link with an invalid
security token and requests a link resend. This page is displayed when the username from
the original link is invalid or if the email of that user has already been verfieid when
the resend request is made
-->
<html>
<head>
<title>Invalid Link</title>
<style type='text/css'>
.container {
border-width: 0px;
display: block;
font: inherit;
font-family: 'Helvetica Neue', Helvetica;
font-size: 16px;
height: 30px;
line-height: 16px;
margin: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
position: relative;
vertical-align: baseline;
}
h1, h2, h3, h4, h5 {
color: #0067AB;
display: block;
font: inherit;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 0 0 15px 0;
padding: 0 0 0 0;
}
</style>
</head>
<body>
<div class="container">
<h1>No link sent. User not found or email already verified</h1>
</div>
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<!-- This page is displayed when someone navigates to a verify email link with an invalid
security token and requests a link resend. This page is displayed when the username
from the original verification link has been found and a new verification link has
been successfully sent to the corresponding stored email
-->
<html>
<head>
<title>Invalid Link</title>
<style type='text/css'>
.container {
border-width: 0px;
display: block;
font: inherit;
font-family: 'Helvetica Neue', Helvetica;
font-size: 16px;
height: 30px;
line-height: 16px;
margin: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
position: relative;
vertical-align: baseline;
}
h1, h2, h3, h4, h5 {
color: #0067AB;
display: block;
font: inherit;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 0 0 15px 0;
padding: 0 0 0 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Link Sent! Check your email.</h1>
</div>
</body>
</html>
+1 -1
View File
@@ -30,4 +30,4 @@ git config user.name "Travis CI"
git config user.email "github@fb.com"
git add .
git commit -m "Deploy master to LATEST branch"
git push --force --quiet "https://${GH_TOKEN}@github.com/parseplatform/parse-server.git" master:latest
git push --force --quiet "https://${GH_TOKEN}@github.com/parse-community/parse-server.git" master:latest
-102
View File
@@ -1,102 +0,0 @@
"""
https://github.com/dmakhno/travis_after_all/blob/master/travis_after_all.py
https://github.com/graphql/graphql-js/blob/master/resources/travis_after_all.py
"""
import os
import json
import time
import logging
try:
import urllib.request as urllib2
except ImportError:
import urllib2
log = logging.getLogger("travis.leader")
log.addHandler(logging.StreamHandler())
log.setLevel(logging.INFO)
TRAVIS_JOB_NUMBER = 'TRAVIS_JOB_NUMBER'
TRAVIS_BUILD_ID = 'TRAVIS_BUILD_ID'
POLLING_INTERVAL = 'LEADER_POLLING_INTERVAL'
build_id = os.getenv(TRAVIS_BUILD_ID)
polling_interval = int(os.getenv(POLLING_INTERVAL, '5'))
#assume, first job is the leader
is_leader = lambda job_number: job_number.endswith('.1')
if not os.getenv(TRAVIS_JOB_NUMBER):
# seems even for builds with only one job, this won't get here
log.fatal("Don't use defining leader for build without matrix")
exit(1)
elif is_leader(os.getenv(TRAVIS_JOB_NUMBER)):
log.info("This is a leader")
else:
#since python is subprocess, env variables are exported back via file
with open(".to_export_back", "w") as export_var:
export_var.write("BUILD_MINION=YES")
log.info("This is a minion")
exit(0)
class MatrixElement(object):
def __init__(self, json_raw):
self.is_finished = json_raw['finished_at'] is not None
self.is_succeeded = json_raw['result'] == 0
self.number = json_raw['number']
self.is_leader = is_leader(self.number)
def matrix_snapshot():
"""
:return: Matrix List
"""
response = urllib2.build_opener().open("https://api.travis-ci.org/builds/{0}".format(build_id)).read()
raw_json = json.loads(response)
matrix_without_leader = [MatrixElement(element) for element in raw_json["matrix"]]
return matrix_without_leader
def wait_others_to_finish():
def others_finished():
"""
Dumps others to finish
Leader cannot finish, it is working now
:return: tuple(True or False, List of not finished jobs)
"""
snapshot = matrix_snapshot()
finished = [el.is_finished for el in snapshot if not el.is_leader]
return reduce(lambda a, b: a and b, finished), [el.number for el in snapshot if
not el.is_leader and not el.is_finished]
while True:
finished, waiting_list = others_finished()
if finished: break
log.info("Leader waits for minions {0}...".format(waiting_list)) # just in case do not get "silence timeout"
time.sleep(polling_interval)
try:
wait_others_to_finish()
final_snapshot = matrix_snapshot()
log.info("Final Results: {0}".format([(e.number, e.is_succeeded) for e in final_snapshot]))
BUILD_AGGREGATE_STATUS = 'BUILD_AGGREGATE_STATUS'
others_snapshot = [el for el in final_snapshot if not el.is_leader]
if reduce(lambda a, b: a and b, [e.is_succeeded for e in others_snapshot]):
os.environ[BUILD_AGGREGATE_STATUS] = "others_succeeded"
elif reduce(lambda a, b: a and b, [not e.is_succeeded for e in others_snapshot]):
log.error("Others Failed")
os.environ[BUILD_AGGREGATE_STATUS] = "others_failed"
else:
log.warn("Others Unknown")
os.environ[BUILD_AGGREGATE_STATUS] = "unknown"
#since python is subprocess, env variables are exported back via file
with open(".to_export_back", "w") as export_var:
export_var.write("BUILD_LEADER=YES {0}={1}".format(BUILD_AGGREGATE_STATUS, os.environ[BUILD_AGGREGATE_STATUS]))
except Exception as e:
log.fatal(e)
+32
View File
@@ -0,0 +1,32 @@
{
"env": {
"jasmine": true
},
"globals": {
"Parse": true,
"reconfigureServer": true,
"createTestUser": true,
"jfail": true,
"ok": true,
"strictEqual": true,
"TestObject": true,
"Item": true,
"Container": true,
"equal": true,
"notEqual": true,
"it_exclude_dbs": true,
"describe_only_db": true,
"describe_only": true,
"on_db": true,
"defaultConfiguration": true,
"expectSuccess": true,
"range": true,
"expectError": true,
"jequal": true,
"create": true,
"arrayContains": true
},
"rules": {
"no-console": [0]
}
}
+183 -183
View File
@@ -5,14 +5,14 @@ const Config = require("../src/Config");
var loginWithWrongCredentialsShouldFail = function(username, password) {
return new Promise((resolve, reject) => {
Parse.User.logIn(username, password)
.then(user => reject('login should have failed'))
.catch(err => {
if (err.message === 'Invalid username/password.') {
resolve();
} else {
reject(err);
}
});
.then(() => reject('login should have failed'))
.catch(err => {
if (err.message === 'Invalid username/password.') {
resolve();
} else {
reject(err);
}
});
});
};
@@ -20,14 +20,14 @@ var isAccountLockoutError = function(username, password, duration, waitTime) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Parse.User.logIn(username, password)
.then(user => 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)') {
resolve();
} else {
reject(err);
}
});
.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)') {
resolve();
} else {
reject(err);
}
});
}, waitTime);
});
};
@@ -39,26 +39,26 @@ describe("Account Lockout Policy: ", () => {
appName: 'unlimited',
publicServerURL: 'http://localhost:1337/1',
})
.then(() => {
var user = new Parse.User();
user.setUsername('username1');
user.setPassword('password');
return user.signUp(null);
})
.then(user => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
})
.then(() => done())
.catch(err => {
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
done();
});
.then(() => {
var user = new Parse.User();
user.setUsername('username1');
user.setPassword('password');
return user.signUp(null);
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
})
.then(() => done())
.catch(err => {
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
done();
});
});
it('throw error if duration is set to an invalid number', done => {
@@ -70,19 +70,19 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config('test');
fail('set duration to an invalid number test failed');
done();
})
.catch(err => {
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
.then(() => {
new Config('test');
fail('set duration to an invalid number test failed');
done();
} else {
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
done();
}
});
})
.catch(err => {
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));
done();
}
});
});
it('throw error if threshold is set to an invalid number', done => {
@@ -94,19 +94,19 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config('test');
fail('set threshold to an invalid number test failed');
done();
})
.catch(err => {
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
.then(() => {
new Config('test');
fail('set threshold to an invalid number test failed');
done();
} else {
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
done();
}
});
})
.catch(err => {
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));
done();
}
});
});
it('throw error if threshold is < 1', done => {
@@ -118,19 +118,19 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config('test');
fail('threshold value < 1 is invalid test failed');
done();
})
.catch(err => {
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
.then(() => {
new Config('test');
fail('threshold value < 1 is invalid test failed');
done();
} else {
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
done();
}
});
})
.catch(err => {
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));
done();
}
});
});
it('throw error if threshold is > 999', done => {
@@ -142,19 +142,19 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config('test');
fail('threshold value > 999 is invalid test failed');
done();
})
.catch(err => {
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
.then(() => {
new Config('test');
fail('threshold value > 999 is invalid test failed');
done();
} else {
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
done();
}
});
})
.catch(err => {
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));
done();
}
});
});
it('throw error if duration is <= 0', done => {
@@ -166,19 +166,19 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config('test');
fail('duration value < 1 is invalid test failed');
done();
})
.catch(err => {
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
.then(() => {
new Config('test');
fail('duration value < 1 is invalid test failed');
done();
} else {
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
done();
}
});
})
.catch(err => {
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));
done();
}
});
});
it('throw error if duration is > 99999', done => {
@@ -190,19 +190,19 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config('test');
fail('duration value > 99999 is invalid test failed');
done();
})
.catch(err => {
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
.then(() => {
new Config('test');
fail('duration value > 99999 is invalid test failed');
done();
} else {
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
done();
}
});
})
.catch(err => {
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));
done();
}
});
});
it('lock account if failed login attempts are above threshold', done => {
@@ -214,28 +214,28 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
var user = new Parse.User();
user.setUsername("username2");
user.setPassword("failedLoginAttemptsThreshold");
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
})
.then(() => {
return isAccountLockoutError('username2', 'wrong password', 1, 1);
})
.then(() => {
.then(() => {
var user = new Parse.User();
user.setUsername("username2");
user.setPassword("failedLoginAttemptsThreshold");
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
})
.then(() => {
return isAccountLockoutError('username2', 'wrong password', 1, 1);
})
.then(() => {
done();
})
.catch(err => {
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
done();
});
})
.catch(err => {
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
done();
});
});
it('lock account for accountPolicy.duration minutes if failed login attempts are above threshold', done => {
@@ -247,32 +247,32 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
var user = new Parse.User();
user.setUsername("username3");
user.setPassword("failedLoginAttemptsThreshold");
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
})
.then(() => {
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
})
.then(() => {
// 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));
done();
});
.then(() => {
var user = new Parse.User();
user.setUsername("username3");
user.setPassword("failedLoginAttemptsThreshold");
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
})
.then(() => {
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
})
.then(() => {
// 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));
done();
});
});
it('allow login for locked account after accountPolicy.duration minutes', done => {
@@ -284,35 +284,35 @@ describe("Account Lockout Policy: ", () => {
},
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
var user = new Parse.User();
user.setUsername("username4");
user.setPassword("correct password");
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
})
.then(() => {
// 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')
.then(user => resolve())
.catch(err => reject(err));
}, 3001);
.then(() => {
var user = new Parse.User();
user.setUsername("username4");
user.setPassword("correct password");
return user.signUp();
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
})
.then(() => {
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
})
.then(() => {
// 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')
.then(() => resolve())
.catch(err => reject(err));
}, 3001);
});
})
.then(() => {
done();
})
.catch(err => {
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
done();
});
})
.then(() => {
done();
})
.catch(err => {
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
done();
});
});
})
+11 -11
View File
@@ -31,7 +31,7 @@ describe("AdaptableController", ()=>{
});
it("should fail setting the wrong adapter to the controller", (done) => {
function WrongAdapter() {};
function WrongAdapter() {}
var adapter = new FilesAdapter();
var controller = new FilesController(adapter);
var otherAdapter = new WrongAdapter();
@@ -42,7 +42,7 @@ describe("AdaptableController", ()=>{
});
it("should fail to instantiate a controller with wrong adapter", (done) => {
function WrongAdapter() {};
function WrongAdapter() {}
var adapter = new WrongAdapter();
expect(() => {
new FilesController(adapter);
@@ -59,10 +59,10 @@ describe("AdaptableController", ()=>{
it("should accept an object adapter", (done) => {
var adapter = {
createFile: function(config, filename, data) { },
deleteFile: function(config, filename) { },
getFileData: function(config, filename) { },
getFileLocation: function(config, filename) { },
createFile: function() { },
deleteFile: function() { },
getFileData: function() { },
getFileLocation: function() { },
}
expect(() => {
new FilesController(adapter);
@@ -71,11 +71,11 @@ describe("AdaptableController", ()=>{
});
it("should accept an object adapter", (done) => {
function AGoodAdapter() {};
AGoodAdapter.prototype.createFile = function(config, filename, data) { };
AGoodAdapter.prototype.deleteFile = function(config, filename) { };
AGoodAdapter.prototype.getFileData = function(config, filename) { };
AGoodAdapter.prototype.getFileLocation = function(config, filename) { };
function AGoodAdapter() {}
AGoodAdapter.prototype.createFile = function() { };
AGoodAdapter.prototype.deleteFile = function() { };
AGoodAdapter.prototype.getFileData = function() { };
AGoodAdapter.prototype.getFileLocation = function() { };
var adapter = new AGoodAdapter();
expect(() => {
+28 -2
View File
@@ -3,6 +3,7 @@ var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
var FilesAdapter = require("parse-server-fs-adapter").default;
var S3Adapter = require("parse-server-s3-adapter").default;
var ParsePushAdapter = require("parse-server-push-adapter").default;
const Config = require('../src/Config');
describe("AdapterLoader", ()=>{
@@ -106,8 +107,9 @@ describe("AdapterLoader", ()=>{
it("should load push adapter from options", (done) => {
var options = {
ios: {
bundleId: 'bundle.id'
android: {
senderId: 'yolo',
apiKey: 'yolo'
}
}
expect(() => {
@@ -118,6 +120,30 @@ describe("AdapterLoader", ()=>{
done();
});
it("should load custom push adapter from string (#3544)", (done) => {
var adapterPath = require('path').resolve("./spec/MockPushAdapter");
var options = {
ios: {
bundleId: 'bundle.id'
}
}
const pushAdapterOptions = {
adapter: adapterPath,
options
};
expect(() => {
reconfigureServer({
push: pushAdapterOptions,
}).then(() => {
const config = new Config(Parse.applicationId);
const pushAdapter = config.pushWorker.adapter;
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
expect(pushAdapter.options).toEqual(pushAdapterOptions);
done();
});
}).not.toThrow();
});
it("should load S3Adapter from direct passing", (done) => {
var s3Adapter = new S3Adapter("key", "secret", "bucket")
expect(() => {
+12 -12
View File
@@ -1,15 +1,15 @@
const analyticsAdapter = {
appOpened: function(parameters, req) {},
trackEvent: function(eventName, parameters, req) {}
appOpened: function() {},
trackEvent: function() {}
}
describe('AnalyticsController', () => {
it('should track a simple event', (done) => {
spyOn(analyticsAdapter, 'trackEvent').and.callThrough();
reconfigureServer({
analyticsAdapter
}).then(() => {
}).then(() => {
return Parse.Analytics.track('MyEvent', {
key: 'value',
count: '0'
@@ -17,7 +17,7 @@ describe('AnalyticsController', () => {
}).then(() => {
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
var lastCall = analyticsAdapter.trackEvent.calls.first();
let args = lastCall.args;
const args = lastCall.args;
expect(args[0]).toEqual('MyEvent');
expect(args[1]).toEqual({
dimensions: {
@@ -26,18 +26,18 @@ describe('AnalyticsController', () => {
}
});
done();
}, (err) => {
}, (err) => {
fail(JSON.stringify(err));
done();
})
});
it('should track a app opened event', (done) => {
spyOn(analyticsAdapter, 'appOpened').and.callThrough();
reconfigureServer({
analyticsAdapter
}).then(() => {
}).then(() => {
return Parse.Analytics.track('AppOpened', {
key: 'value',
count: '0'
@@ -45,7 +45,7 @@ describe('AnalyticsController', () => {
}).then(() => {
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
var lastCall = analyticsAdapter.appOpened.calls.first();
let args = lastCall.args;
const args = lastCall.args;
expect(args[0]).toEqual({
dimensions: {
key: 'value',
@@ -53,9 +53,9 @@ describe('AnalyticsController', () => {
}
});
done();
}, (err) => {
}, (err) => {
fail(JSON.stringify(err));
done();
})
})
})
})
+337
View File
@@ -0,0 +1,337 @@
var auth = require('../src/Auth');
var Config = require('../src/Config');
var rest = require('../src/rest');
var AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
describe('AudiencesRouter', () => {
it('uses find condition from request.body', (done) => {
var config = new Config('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
var iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
var request = {
config: config,
auth: auth.master(config),
body: {
where: {
query: '{ "test": "android" }'
}
},
query: {},
info: {}
};
var 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) => {
var results = res.response.results;
expect(results.length).toEqual(1);
done();
})
.catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('uses find condition from request.query', (done) => {
var config = new Config('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
var iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
var request = {
config: config,
auth: auth.master(config),
body: {},
query: {
where: {
'query': '{ "test": "android" }'
}
},
info: {}
};
var 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) => {
var results = res.response.results;
expect(results.length).toEqual(1);
done();
})
.catch((err) => {
fail(err);
done();
});
});
it('query installations with limit = 0', (done) => {
var config = new Config('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
var iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
var request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0
},
info: {}
};
new Config('test');
var 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) => {
var response = res.response;
expect(response.results.length).toEqual(0);
done();
})
.catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('query installations with count = 1', done => {
var config = new Config('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
var iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
var request = {
config: config,
auth: auth.master(config),
body: {},
query: {
count: 1
},
info: {}
};
var router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
.then(() => router.handleFind(request))
.then((res) => {
var response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(2);
done();
})
.catch(error => {
fail(JSON.stringify(error));
done();
})
});
it('query installations with limit = 0 and count = 1', (done) => {
var config = new Config('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
var iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
var request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0,
count: 1
},
info: {}
};
var 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) => {
var 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) => {
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) => {
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) => {
expect(results.results.length).toEqual(0);
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 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 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 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 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 = (new Config(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
}
},
{},
(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) => {
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); })
});
});
});
});
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); })
})
});
});
+3 -4
View File
@@ -4,7 +4,6 @@ describe('Auth', () => {
describe('getUserRoles', () => {
var auth;
var config;
var cacheController;
var currentRoles = null;
var currentUserId = 'userId';
@@ -51,8 +50,8 @@ describe('Auth', () => {
expect(roles).toEqual(currentRoles);
return auth.getUserRoles()
})
.then((roles) => auth.getUserRoles())
.then((roles) => auth.getUserRoles())
.then(() => auth.getUserRoles())
.then(() => auth.getUserRoles())
.then((roles) => {
// Should only call the cache adapter once.
expect(config.cacheController.role.get.calls.count()).toEqual(1);
@@ -79,7 +78,7 @@ describe('Auth', () => {
.then(() => done());
});
it('should properly handle bcrypt upgrade', (done) => {
it('should properly handle bcrypt upgrade', (done) => {
var bcryptOriginal = require('bcrypt-nodejs');
var bcryptNew = require('bcryptjs');
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
+348
View File
@@ -0,0 +1,348 @@
var request = require('request');
var Config = require("../src/Config");
var defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
var authenticationLoader = require('../src/Adapters/Auth');
var path = require('path');
describe('AuthenticationProviders', function() {
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
it("Should validate structure of " + providerName, (done) => {
var provider = require("../src/Adapters/Auth/" + providerName);
jequal(typeof provider.validateAuthData, "function");
jequal(typeof provider.validateAppId, "function");
const authDataPromise = provider.validateAuthData({}, {});
const validateAppIdPromise = provider.validateAppId("app", "key", {});
jequal(authDataPromise.constructor, Promise.prototype.constructor);
jequal(validateAppIdPromise.constructor, Promise.prototype.constructor);
authDataPromise.then(()=>{}, ()=>{});
validateAppIdPromise.then(()=>{}, ()=>{});
done();
});
});
var getMockMyOauthProvider = function() {
return {
authData: {
id: "12345",
access_token: "12345",
expiration_date: new Date().toJSON(),
},
shouldError: false,
loggedOut: false,
synchronizedUserId: null,
synchronizedAuthToken: null,
synchronizedExpiration: null,
authenticate: function(options) {
if (this.shouldError) {
options.error(this, "An error occurred");
} else if (this.shouldCancel) {
options.error(this, null);
} else {
options.success(this, this.authData);
}
},
restoreAuthentication: function(authData) {
if (!authData) {
this.synchronizedUserId = null;
this.synchronizedAuthToken = null;
this.synchronizedExpiration = null;
return true;
}
this.synchronizedUserId = authData.id;
this.synchronizedAuthToken = authData.access_token;
this.synchronizedExpiration = authData.expiration_date;
return true;
},
getAuthType: function() {
return "myoauth";
},
deauthenticate: function() {
this.loggedOut = true;
this.restoreAuthentication(null);
}
};
};
Parse.User.extend({
extended: function() {
return true;
}
});
var createOAuthUser = function(callback) {
return createOAuthUserWithSessionToken(undefined, callback);
}
var createOAuthUserWithSessionToken = function(token, callback) {
var jsonBody = {
authData: {
myoauth: getMockMyOauthProvider().authData
}
};
var options = {
headers: {'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Installation-Id': 'yolo',
'X-Parse-Session-Token': token,
'Content-Type': 'application/json' },
url: 'http://localhost:8378/1/users',
body: jsonBody,
json: true
};
return new Promise((resolve) => {
request.post(options, (err, res, body) => {
resolve({err, res, body});
if (callback) {
callback(err, res, body);
}
});
});
}
it("should create user with REST API", done => {
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
var b = body;
ok(b.sessionToken);
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
var sessionToken = b.sessionToken;
var q = new Parse.Query("_Session");
q.equalTo('sessionToken', sessionToken);
q.first({useMasterKey: true}).then((res) => {
if (!res) {
fail('should not fail fetching the session');
done();
return;
}
expect(res.get("installationId")).toEqual('yolo');
done();
}).fail(() => {
fail('should not fail fetching the session');
done();
})
});
});
it("should only create a single user with REST API", (done) => {
var objectId;
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
var b = body
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
objectId = b.objectId;
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
var b = body;
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
expect(b.objectId).toBe(objectId);
done();
});
});
});
it("should fail to link if session token don't match user", (done) => {
Parse.User.signUp('myUser', 'password').then((user) => {
return createOAuthUserWithSessionToken(user.getSessionToken());
}).then(() => {
return Parse.User.logOut();
}).then(() => {
return Parse.User.signUp('myUser2', 'password');
}).then((user) => {
return createOAuthUserWithSessionToken(user.getSessionToken());
}).then(({ body }) => {
expect(body.code).toBe(208);
expect(body.error).toBe('this auth is already used');
done();
}).catch(done.fail);
});
it("unlink and link with custom provider", (done) => {
var provider = getMockMyOauthProvider();
Parse.User._registerAuthenticationProvider(provider);
Parse.User._logInWith("myoauth", {
success: function(model) {
ok(model instanceof Parse.User, "Model should be a Parse.User");
strictEqual(Parse.User.current(), model);
ok(model.extended(), "Should have used the subclass.");
strictEqual(provider.authData.id, provider.synchronizedUserId);
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
model._unlinkFrom("myoauth", {
success: function(model) {
ok(!model._isLinked("myoauth"),
"User should not be linked to myoauth");
ok(!provider.synchronizedUserId, "User id should be cleared");
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
ok(!provider.synchronizedExpiration,
"Expiration should be cleared");
// make sure the auth data is properly deleted
var config = new Config(Parse.applicationId);
config.database.adapter.find('_User', {
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
}, { objectId: model.id }, {})
.then(res => {
expect(res.length).toBe(1);
expect(res[0]._auth_data_myoauth).toBeUndefined();
expect(res[0]._auth_data_myoauth).not.toBeNull();
model._linkWith("myoauth", {
success: function(model) {
ok(provider.synchronizedUserId, "User id should have a value");
ok(provider.synchronizedAuthToken,
"Auth token should have a value");
ok(provider.synchronizedExpiration,
"Expiration should have a value");
ok(model._isLinked("myoauth"),
"User should be linked to myoauth");
done();
},
error: function() {
ok(false, "linking again should succeed");
done();
}
});
});
},
error: function() {
ok(false, "unlinking should succeed");
done();
}
});
},
error: function() {
ok(false, "linking should have worked");
done();
}
});
});
function validateValidator(validator) {
expect(typeof validator).toBe('function');
}
function validateAuthenticationHandler(authenticationHandler) {
expect(authenticationHandler).not.toBeUndefined();
expect(typeof authenticationHandler.getValidatorForProvider).toBe('function');
expect(typeof authenticationHandler.getValidatorForProvider).toBe('function');
}
function validateAuthenticationAdapter(authAdapter) {
expect(authAdapter).not.toBeUndefined();
if (!authAdapter) { return; }
expect(typeof authAdapter.validateAuthData).toBe('function');
expect(typeof authAdapter.validateAppId).toBe('function');
}
it('properly loads custom adapter', (done) => {
var validAuthData = {
id: 'hello',
token: 'world'
}
const adapter = {
validateAppId: function() {
return Promise.resolve();
},
validateAuthData: function(authData) {
if (authData.id == validAuthData.id && authData.token == validAuthData.token) {
return Promise.resolve();
}
return Promise.reject();
}
};
const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough();
const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough();
const authenticationHandler = authenticationLoader({
customAuthentication: adapter
});
validateAuthenticationHandler(authenticationHandler);
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
validateValidator(validator);
validator(validAuthData).then(() => {
expect(authDataSpy).toHaveBeenCalled();
// AppIds are not provided in the adapter, should not be called
expect(appIdSpy).not.toHaveBeenCalled();
done();
}, (err) => {
jfail(err);
done();
})
});
it('properly loads custom adapter module object', (done) => {
const authenticationHandler = authenticationLoader({
customAuthentication: path.resolve('./spec/support/CustomAuth.js')
});
validateAuthenticationHandler(authenticationHandler);
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
validateValidator(validator);
validator({
token: 'my-token'
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
})
});
it('properly loads custom adapter module object', (done) => {
const authenticationHandler = authenticationLoader({
customAuthentication: { module: path.resolve('./spec/support/CustomAuthFunction.js'), options: { token: 'valid-token' }}
});
validateAuthenticationHandler(authenticationHandler);
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
validateValidator(validator);
validator({
token: 'valid-token'
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
})
});
it('properly loads a default adapter with options', () => {
const options = {
facebook: {
appIds: ['a', 'b']
}
};
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('facebook', options);
validateAuthenticationAdapter(adapter);
expect(appIds).toEqual(['a', 'b']);
expect(providerOptions).toEqual(options.facebook);
});
it('properly loads a custom adapter with options', () => {
const options = {
custom: {
validateAppId: () => {},
validateAuthData: () => {},
appIds: ['a', 'b']
}
};
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('custom', options);
validateAuthenticationAdapter(adapter);
expect(appIds).toEqual(['a', 'b']);
expect(providerOptions).toEqual(options.custom);
});
});
+122 -75
View File
@@ -1,29 +1,31 @@
'use strict';
var commander = require("../src/cli/utils/commander").default;
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';
var definitions = {
"arg0": "PROGRAM_ARG_0",
"arg1": {
env: "PROGRAM_ARG_1",
var testDefinitions = {
'arg0': 'PROGRAM_ARG_0',
'arg1': {
env: 'PROGRAM_ARG_1',
required: true
},
"arg2": {
env: "PROGRAM_ARG_2",
'arg2': {
env: 'PROGRAM_ARG_2',
action: function(value) {
var value = parseInt(value);
if (!Number.isInteger(value)) {
throw "arg2 is invalid";
var intValue = parseInt(value);
if (!Number.isInteger(intValue)) {
throw 'arg2 is invalid';
}
return value;
return intValue;
}
},
"arg3": {},
"arg4": {
default: "arg4Value"
'arg3': {},
'arg4': {
default: 'arg4Value'
}
}
};
describe("commander additions", () => {
describe('commander additions', () => {
afterEach((done) => {
commander.options = [];
delete commander.arg0;
@@ -32,107 +34,152 @@ describe("commander additions", () => {
delete commander.arg3;
delete commander.arg4;
done();
})
});
it("should load properly definitions from args", (done) => {
commander.loadDefinitions(definitions);
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg1", "arg1Value", "--arg2", "2", "--arg3", "some"]);
expect(commander.arg0).toEqual("arg0Value");
expect(commander.arg1).toEqual("arg1Value");
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']);
expect(commander.arg0).toEqual('arg0Value');
expect(commander.arg1).toEqual('arg1Value');
expect(commander.arg2).toEqual(2);
expect(commander.arg3).toEqual("some");
expect(commander.arg4).toEqual("arg4Value");
expect(commander.arg3).toEqual('some');
expect(commander.arg4).toEqual('arg4Value');
done();
});
it("should load properly definitions from env", (done) => {
commander.loadDefinitions(definitions);
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");
expect(commander.arg0).toEqual('arg0ENVValue');
expect(commander.arg1).toEqual('arg1ENVValue');
expect(commander.arg2).toEqual(3);
expect(commander.arg4).toEqual("arg4Value");
expect(commander.arg4).toEqual('arg4Value');
done();
});
it("should load properly use args over env", (done) => {
commander.loadDefinitions(definitions);
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg4", "anotherArg4"], {
"PROGRAM_ARG_0": "arg0ENVValue",
"PROGRAM_ARG_1": "arg1ENVValue",
"PROGRAM_ARG_2": "4",
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'
});
expect(commander.arg0).toEqual("arg0Value");
expect(commander.arg1).toEqual("arg1ENVValue");
expect(commander.arg0).toEqual('arg0Value');
expect(commander.arg1).toEqual('arg1ENVValue');
expect(commander.arg2).toEqual(4);
expect(commander.arg4).toEqual("anotherArg4");
expect(commander.arg4).toEqual('');
done();
});
it("should fail in action as port is invalid", (done) => {
commander.loadDefinitions(definitions);
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",
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value'], {
'PROGRAM_ARG_0': 'arg0ENVValue',
'PROGRAM_ARG_1': 'arg1ENVValue',
'PROGRAM_ARG_2': 'hello',
});
}).toThrow("arg2 is invalid");
}).toThrow('arg2 is invalid');
done();
});
it("should not override config.json", (done) => {
commander.loadDefinitions(definitions);
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "./spec/configs/CLIConfig.json"], {
"PROGRAM_ARG_0": "arg0ENVValue",
"PROGRAM_ARG_1": "arg1ENVValue",
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',
});
let options = commander.getOptions();
const options = commander.getOptions();
expect(options.arg2).toBe(8888);
expect(options.arg3).toBe("hello"); //config value
expect(options.arg3).toBe('hello'); //config value
expect(options.arg4).toBe('/1');
done();
});
it("should fail with invalid values in JSON", (done) => {
commander.loadDefinitions(definitions);
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")
}).toThrow('arg2 is invalid');
done();
});
it("should fail when too many apps are set", (done) => {
commander.loadDefinitions(definitions);
it('should fail when too many apps are set', (done) => {
commander.loadDefinitions(testDefinitions);
expect(() => {
commander.parse(["node","./CLI.spec.js","./spec/configs/CLIConfigFailTooManyApps.json"]);
}).toThrow("Multiple apps are not supported")
commander.parse(['node','./CLI.spec.js','./spec/configs/CLIConfigFailTooManyApps.json']);
}).toThrow('Multiple apps are not supported');
done();
});
it("should load config from apps", (done) => {
commander.loadDefinitions(definitions);
commander.parse(["node", "./CLI.spec.js", "./spec/configs/CLIConfigApps.json"]);
let options = commander.getOptions();
expect(options.arg1).toBe("my_app");
it('should load config from apps', (done) => {
commander.loadDefinitions(testDefinitions);
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);
expect(options.arg3).toBe("hello"); //config value
expect(options.arg3).toBe('hello'); //config value
expect(options.arg4).toBe('/1');
done();
});
it("should fail when passing an invalid arguement", (done) => {
commander.loadDefinitions(definitions);
it('should fail when passing an invalid arguement', (done) => {
commander.loadDefinitions(testDefinitions);
expect(() => {
commander.parse(["node", "./CLI.spec.js", "./spec/configs/CLIConfigUnknownArg.json"]);
}).toThrow('error: unknown option myArg')
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigUnknownArg.json']);
}).toThrow('error: unknown option myArg');
done();
});
});
describe('definitions', () => {
it('should have valid types', () => {
for (const key in definitions) {
const definition = definitions[key];
expect(typeof definition).toBe('object');
if (typeof definition.env !== 'undefined') {
expect(typeof definition.env).toBe('string');
}
expect(typeof definition.help).toBe('string');
if (typeof definition.required !== 'undefined') {
expect(typeof definition.required).toBe('boolean');
}
if (typeof definition.action !== 'undefined') {
expect(typeof definition.action).toBe('function');
}
}
});
it('should throw when using deprecated facebookAppIds', () => {
expect(() => {
definitions.facebookAppIds.action()
}).toThrow();
});
});
describe('LiveQuery definitions', () => {
it('should have valid types', () => {
for (const key in liveQueryDefinitions) {
const definition = liveQueryDefinitions[key];
expect(typeof definition).toBe('object');
if (typeof definition.env !== 'undefined') {
expect(typeof definition.env).toBe('string');
}
expect(typeof definition.help).toBe('string');
if (typeof definition.required !== 'undefined') {
expect(typeof definition.required).toBe('boolean');
}
if (typeof definition.action !== 'undefined') {
expect(typeof definition.action).toBe('function');
}
}
});
});
+5 -5
View File
@@ -62,13 +62,13 @@ describe('CacheController', function() {
it('should handle cache rejections', (done) => {
FakeCacheAdapter.get = () => Promise.reject();
FakeCacheAdapter.get = () => Promise.reject();
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
cache.get('foo').then(done, () => {
fail('Promise should not be rejected.');
});
cache.get('foo').then(done, () => {
fail('Promise should not be rejected.');
});
});
});
+23 -23
View File
@@ -1,31 +1,31 @@
var ClientSDK = require('../src/ClientSDK');
describe('ClientSDK', () => {
it('should properly parse the SDK versions', () => {
let clientSDKFromVersion = ClientSDK.fromString;
expect(clientSDKFromVersion('i1.1.1')).toEqual({
sdk: 'i',
version: '1.1.1'
});
expect(clientSDKFromVersion('i1')).toEqual({
sdk: 'i',
version: '1'
});
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
sdk: 'apple-tv',
version: '1.13.0'
});
expect(clientSDKFromVersion('js1.9.0')).toEqual({
sdk: 'js',
version: '1.9.0'
});
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'
});
expect(clientSDKFromVersion('i1')).toEqual({
sdk: 'i',
version: '1'
});
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
sdk: 'apple-tv',
version: '1.13.0'
});
expect(clientSDKFromVersion('js1.9.0')).toEqual({
sdk: 'js',
version: '1.9.0'
});
});
it('should properly sastisfy', () => {
it('should properly sastisfy', () => {
expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js1.9.0")).toBe(true);
expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js2.0.0")).toBe(true);
@@ -38,4 +38,4 @@ describe('ClientSDK', () => {
js: '>=1.9.0'
})(undefined)).toBe(true);
})
})
})
+712 -376
View File
File diff suppressed because it is too large Load Diff
+211 -194
View File
@@ -1,229 +1,246 @@
'use strict';
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const fs = require('fs');
const loremFile = __dirname + '/support/lorem.txt';
describe("Cloud Code Logger", () => {
let user;
let user;
beforeEach(done => {
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())
});
beforeEach(done => {
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())
});
});
// Note that helpers takes care of logout.
// see helpers.js:afterEach
it("should expose log to functions", done => {
var logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define("loggerTest", (req, res) => {
req.log.info('logTest', 'info log', { info: 'some log' });
req.log.error('logTest', 'error log', { error: 'there was an error' });
res.success({});
});
// Note that helpers takes care of logout.
// see helpers.js:afterEach
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();
});
});
it("should expose log to functions", done => {
var logController = new LoggerController(new WinstonLoggerAdapter());
it('trigger should obfuscate password', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define("loggerTest", (req, res) => {
req.log.info('logTest', 'info log', { info: 'some log' });
req.log.error('logTest', 'error log', { error: 'there was an error' });
res.success({});
});
Parse.Cloud.run('loggerTest').then(() => {
return logController.getLogs({ from: Date.now() - 500, size: 1000 });
}).then((res) => {
expect(res.length).not.toBe(0);
let lastLogs = res.slice(0, 3);
let cloudFunctionMessage = lastLogs[0];
let errorMessage = lastLogs[1];
let infoMessage = lastLogs[2];
expect(cloudFunctionMessage.level).toBe('info');
expect(cloudFunctionMessage.params).toEqual({});
expect(cloudFunctionMessage.message).toMatch(/Ran cloud function loggerTest for user [^ ]* with:\n Input: {}\n 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();
});
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
res.success(req.object);
});
it('trigger should obfuscate password', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
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(/\*\*\*\*\*\*\*\*/);
done();
})
.then(null, e => done.fail(e));
});
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
res.success(req.object);
});
it("should expose log to trigger", (done) => {
var logController = new LoggerController(new WinstonLoggerAdapter());
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(/\*\*\*\*\*\*\*\*/);
done();
})
.then(null, e => done.fail(e));
Parse.Cloud.beforeSave("MyObject", (req, res) => {
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
res.success({});
});
it("should expose log to trigger", (done) => {
var logController = new LoggerController(new WinstonLoggerAdapter());
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');
done();
});
});
Parse.Cloud.beforeSave("MyObject", (req, res) => {
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
res.success({});
});
it('should truncate really long lines when asked to', () => {
const logController = new LoggerController(new WinstonLoggerAdapter());
const longString = fs.readFileSync(loremFile, 'utf8');
const truncatedString = logController.truncateLogMessage(longString);
expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)'
});
let 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);
let lastLogs = res.slice(0, 3);
let cloudTriggerMessage = lastLogs[0];
let errorMessage = lastLogs[1];
let infoMessage = lastLogs[2];
expect(cloudTriggerMessage.level).toBe('info');
expect(cloudTriggerMessage.triggerType).toEqual('beforeSave');
expect(cloudTriggerMessage.message).toMatch(/beforeSave triggered for MyObject for user [^ ]*\n Input: {}\n 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');
done();
});
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);
});
it('should truncate really long lines when asked to', () => {
const logController = new LoggerController(new WinstonLoggerAdapter());
const longString = fs.readFileSync(loremFile, 'utf8');
const truncatedString = logController.truncateLogMessage(longString);
expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)'
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);
done();
})
.then(null, e => done.fail(e));
});
it('should log an afterSave', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
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');
done();
})
// 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!');
});
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);
});
new Parse.Object('MyObject')
.save()
.then(
() => 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()
});
});
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 Input: {.*?\(truncated\)$/m);
done();
})
.then(null, e => done.fail(e));
it('should log cloud function success', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define('aFunction', (req, res) => {
res.success('it worked!');
});
it('should log an afterSave', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.afterSave("MyObject", (req) => { });
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');
done();
})
// catch errors - not that the error is actually useful :(
.then(null, e => done.fail(e));
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();
});
});
it('should log cloud function failure', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define('aFunction', (req, res) => {
res.error('it failed!');
});
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.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!"}/);
done();
});
});
new Parse.Object('MyObject')
.save()
.then(
() => done.fail('this is not supposed to succeed'),
e => 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()
});
xit('should log a changed beforeSave indicating a change', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave("MyObject", (req, res) => {
const myObj = req.object;
myObj.set('aChange', true);
res.success(myObj);
});
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.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 Input: {"foo":"bar"}\n 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.run('aFunction', { foo: 'bar' })
.then(null, () => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(logs => {
const log = logs[1];
expect(log.level).toEqual('error');
expect(log.message).toMatch(
/Failed running cloud function aFunction for user [^ ]* with:\n Input: {"foo":"bar"}\n Error: {"code":141,"message":"it failed!"}/);
done();
});
});
xit('should log a changed beforeSave indicating a change', done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave("MyObject", (req, res) => {
const myObj = req.object;
myObj.set('aChange', true);
res.success(myObj);
});
new Parse.Object('MyObject')
.save()
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(logs => {
// expect the log to indicate that it has changed
/*
new Parse.Object('MyObject')
.save()
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(() => {
// expect the log to indicate that it has changed
/*
Here's what it looks like on parse.com...
Input: {"original":{"clientVersion":"1","createdAt":"2016-06-02T05:29:08.694Z","image":{"__type":"File","name":"tfss-xxxxxxxx.png","url":"http://files.parsetfss.com/xxxxxxxx.png"},"lastScanDate":{"__type":"Date","iso":"2016-06-02T05:28:58.135Z"},"localIdentifier":"XXXXX","objectId":"OFHMX7ZUcI","status":... (truncated)
Result: Update changed to {"object":{"__type":"Pointer","className":"Emoticode","objectId":"ksrq7z3Ehc"},"imageThumb":{"__type":"File","name":"tfss-xxxxxxx.png","url":"http://files.parsetfss.com/xxxxx.png"},"status":"success"}
*/
done();
})
.then(null, e => done.fail(JSON.stringify(e)));
}).pend('needs more work.....');
done();
})
.then(null, e => done.fail(JSON.stringify(e)));
}).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.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(/\*\*\*\*\*\*\*\*/);
done();
})
.then(null, e => done.fail(e));
});
});
+51
View File
@@ -0,0 +1,51 @@
var DatabaseController = require('../src/Controllers/DatabaseController.js');
var validateQuery = DatabaseController._validateQuery;
describe('DatabaseController', function() {
describe('validateQuery', function() {
it('should restructure simple cases of SERVER-13732', (done) => {
var 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) => {
var query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}});
query = {$or: [{a: 1}, {b: 1}], c: {$near: {}}};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$near: {}}});
done();
});
it('should push refactored keys down a tree for SERVER-13732', (done) => {
var 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();
});
});
});
+544 -292
View File
@@ -1,12 +1,12 @@
"use strict";
const MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
const request = require('request');
const requestp = require('request-promise');
const Config = require('../src/Config');
describe("Email Verification Token Expiration: ", () => {
it('show the invalid link page, if the user clicks on the verify email link after the email verify token expires', done => {
it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
@@ -23,28 +23,28 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
.then(() => {
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
setTimeout(() => {
expect(sendEmailOptions).not.toBeUndefined();
setTimeout(() => {
expect(sendEmailOptions).not.toBeUndefined();
request.get(sendEmailOptions.link, {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
done();
});
}, 1000);
}).catch((err) => {
jfail(err);
done();
});
}, (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');
done();
});
}, 1000);
}).catch((err) => {
jfail(err);
done();
});
});
it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', done => {
@@ -64,35 +64,35 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
.then(() => {
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
setTimeout(() => {
expect(sendEmailOptions).not.toBeUndefined();
setTimeout(() => {
expect(sendEmailOptions).not.toBeUndefined();
request.get(sendEmailOptions.link, {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
done();
})
.catch((err) => {
jfail(error);
done();
}, (error, response) => {
expect(response.statusCode).toEqual(302);
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
done();
})
.catch(() => {
jfail(error);
done();
});
});
});
}, 1000);
}).catch((err) => {
jfail(error);
done();
});
}, 1000);
}).catch((error) => {
jfail(error);
done();
});
});
it('if user clicks on the email verify link before email verification token expiration then show the verify email success page', done => {
@@ -112,23 +112,23 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
}, (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');
done();
});
}).catch((error) => {
jfail(error);
done();
});
}).catch((err) => {
jfail(error);
done();
});
});
it('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', done => {
@@ -148,30 +148,30 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(true);
done();
})
.catch((err) => {
jfail(error);
done();
}, (error, response) => {
expect(response.statusCode).toEqual(302);
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(true);
done();
})
.catch((error) => {
jfail(error);
done();
});
});
}).catch((error) => {
jfail(error);
done();
});
}).catch((err) => {
jfail(error);
done();
});
});
it('if user clicks on the email verify link before email verification token expiration then user should be able to login', done => {
@@ -191,31 +191,31 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
}).then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
.then(user => {
expect(typeof user).toBe('object');
expect(user.get('emailVerified')).toBe(true);
done();
})
.catch((error) => {
jfail(error);
done();
}, (error, response) => {
expect(response.statusCode).toEqual(302);
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
.then(user => {
expect(typeof user).toBe('object');
expect(user.get('emailVerified')).toBe(true);
done();
})
.catch((error) => {
jfail(error);
done();
});
});
}).catch((error) => {
jfail(error);
done();
});
}).catch((err) => {
jfail(error);
done();
});
});
it('sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', done => {
@@ -235,29 +235,30 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
})
.then(() => {
user.setUsername('sets_email_verify_token_expires_at');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
let config = new Config('test');
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
})
.then(results => {
expect(results.length).toBe(1);
let user = results[0];
expect(typeof user).toBe('object');
expect(user.emailVerified).toEqual(false);
expect(typeof user._email_verify_token).toBe('string');
expect(typeof user._email_verify_token_expires_at).toBe('object');
done();
})
.catch(error => {
jfail(error);
done();
});
.then(() => {
user.setUsername('sets_email_verify_token_expires_at');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
const config = new Config('test');
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
})
.then(results => {
expect(results.length).toBe(1);
const user = results[0];
expect(typeof user).toBe('object');
expect(user.emailVerified).toEqual(false);
expect(typeof user._email_verify_token).toBe('string');
expect(typeof user._email_verify_token_expires_at).toBe('object');
expect(sendEmailOptions).toBeDefined();
done();
})
.catch(error => {
jfail(error);
done();
});
});
it('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => {
@@ -277,98 +278,42 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
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, {
.then(() => {
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, body) => {
expect(response.statusCode).toEqual(302);
let config = new Config('test');
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');
done();
})
.catch(error => {
jfail(error);
done();
});
});
})
.catch(error => {
jfail(error);
done();
});
});
it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
var serverConfig = {
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
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.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) => {
}, (error, response) => {
expect(response.statusCode).toEqual(302);
resolve(user.fetch());
const config = new Config('test');
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');
done();
})
.catch(error => {
jfail(error);
done();
});
});
});
})
.then(() => {
expect(user.get('emailVerified')).toEqual(true);
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
return reconfigureServer(serverConfig);
})
.then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
})
.catch(error => {
jfail(error);
done();
});
})
.catch((err) => {
jfail(error);
done();
});
});
it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
@@ -387,53 +332,107 @@ describe("Email Verification Token Expiration: ", () => {
// setup server WITHOUT enabling the expire email verify token flag
reconfigureServer(serverConfig)
.then(() => {
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
return user.fetch();
})
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
return reconfigureServer(serverConfig);
})
.then(() => {
request.get(sendEmailOptions.link, {
.then(() => {
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());
});
});
})
.then(() => {
expect(user.get('emailVerified')).toEqual(true);
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
return reconfigureServer(serverConfig);
})
.then(() => {
request.get(sendEmailOptions.link, {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
}, (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');
done();
});
})
.catch((error) => {
jfail(error);
done();
});
});
it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
var serverConfig = {
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
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.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
// just get the user again - DO NOT email verify the user
return user.fetch();
})
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
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');
done();
});
})
.catch((error) => {
jfail(error);
done();
});
})
.catch((err) => {
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 => {
let db;
let user = new Parse.User();
const user = new Parse.User();
let userBeforeEmailReset;
let sendEmailOptions;
let emailAdapter = {
const emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
};
let serverConfig = {
const serverConfig = {
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
@@ -442,45 +441,297 @@ describe("Email Verification Token Expiration: ", () => {
};
reconfigureServer(serverConfig)
.then(() => {
user.setUsername("newEmailVerifyTokenOnEmailReset");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
let config = new Config('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
});
})
.then(userFromDb => {
expect(typeof userFromDb).toBe('object');
userBeforeEmailReset = userFromDb;
.then(() => {
user.setUsername("newEmailVerifyTokenOnEmailReset");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
const config = new Config('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
});
})
.then(userFromDb => {
expect(typeof userFromDb).toBe('object');
userBeforeEmailReset = userFromDb;
// trigger another token generation by setting the email
user.set('email', 'user@parse.com');
return new Promise((resolve, reject) => {
// 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
setTimeout( () => resolve(user.save()), 500 );
setTimeout(() => resolve(user.save()), 500);
});
})
.then(() => {
const config = new Config('test');
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(sendEmailOptions).toBeDefined();
done();
})
.catch((error) => {
jfail(error);
done();
});
});
it('should send a new verification email when a resend is requested and the user is UNVERIFIED', done => {
var user = new Parse.User();
var sendEmailOptions;
var sendVerificationEmailCallCount = 0;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
})
.then(() => {
let config = new Config('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
.then(() => {
user.setUsername('resends_verification_token');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
expect(sendVerificationEmailCallCount).toBe(1);
return requestp.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
body: {
email: 'user@parse.com'
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: true,
resolveWithFullResponse: true,
simple: false // this promise is only rejected if the call itself failed
})
.then((response) => {
expect(response.statusCode).toBe(200);
expect(sendVerificationEmailCallCount).toBe(2);
expect(sendEmailOptions).toBeDefined();
done();
});
})
.catch(error => {
jfail(error);
done();
});
});
it('should not send a new verification email when a resend is requested and the user is VERIFIED', done => {
var user = new Parse.User();
var sendEmailOptions;
var sendVerificationEmailCallCount = 0;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
})
.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);
done();
.then(() => {
user.setUsername('no_new_verification_token_once_verified');
user.setPassword('expiringToken');
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
return requestp.get({
url: sendEmailOptions.link,
followRedirect: false,
resolveWithFullResponse: true,
simple: false
})
.then((response) => {
expect(response.statusCode).toEqual(302);
});
})
.then(() => {
expect(sendVerificationEmailCallCount).toBe(1);
return requestp.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
body: {
email: 'user@parse.com'
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: true,
resolveWithFullResponse: true,
simple: false // this promise is only rejected if the call itself failed
})
.then((response) => {
expect(response.statusCode).toBe(400);
expect(sendVerificationEmailCallCount).toBe(1);
done();
});
})
.catch(error => {
jfail(error);
done();
});
});
it('should not send a new verification email if this user does not exist', done => {
var sendEmailOptions;
var sendVerificationEmailCallCount = 0;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
})
.catch((err) => {
jfail(error);
done();
});
.then(() => {
return requestp.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
body: {
email: 'user@parse.com'
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: true,
resolveWithFullResponse: true,
simple: false
})
.then(response => {
expect(response.statusCode).toBe(400);
expect(sendVerificationEmailCallCount).toBe(0);
expect(sendEmailOptions).not.toBeDefined();
done();
});
})
.catch(error => {
jfail(error);
done();
});
});
it('should fail if no email is supplied', done => {
var sendEmailOptions;
var sendVerificationEmailCallCount = 0;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
})
.then(() => {
request.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
body: {},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
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();
});
})
.catch(error => {
jfail(error);
done();
});
});
it('should fail if email is not a string', done => {
var sendEmailOptions;
var sendVerificationEmailCallCount = 0;
var emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
}
reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1'
})
.then(() => {
request.post({
uri: 'http://localhost:8378/1/verificationEmailRequest',
body: {email: 3},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
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();
});
})
.catch(error => {
jfail(error);
done();
});
});
it('client should not see the _email_verify_token_expires_at field', done => {
@@ -500,29 +751,30 @@ describe("Email Verification Token Expiration: ", () => {
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => {
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
done();
user.setUsername("testEmailVerifyTokenValidity");
user.setPassword("expiringToken");
user.set('email', 'user@parse.com');
return user.signUp();
})
.catch(error => {
.then(() => {
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
expect(sendEmailOptions).toBeDefined();
done();
})
.catch(error => {
jfail(error);
done();
});
}).catch((error) => {
jfail(error);
done();
});
}).catch((err) => {
jfail(error);
done();
});
});
})
+1 -1
View File
@@ -27,7 +27,7 @@ describe('Enable single schema cache', () => {
config = fakeRequestForConfig();
nobody = auth.nobody(config);
return rest.find(config, nobody, 'Bar', {type: 1});
}).then((response) => {
}).then(() => {
fail('Should throw error');
done();
}, (error) => {
+1 -1
View File
@@ -25,7 +25,7 @@ describe('EventEmitterPubSub', function() {
subscriber.unsubscribe('testChannel');
// Register mock checked for subscriber
var isCalled = false;
subscriber.on('message', function(channel, message) {
subscriber.on('message', function() {
isCalled = true;
});
+39 -4
View File
@@ -1,7 +1,17 @@
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
var Config = require("../src/Config");
var FilesController = require('../src/Controllers/FilesController').default;
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 mockAdapter = {
createFile: () => {
return Parse.Promise.reject(new Error('it failed'));
},
deleteFile: () => { },
getFileData: () => { },
getFileLocation: () => 'xyz'
}
// Small additional tests to improve overall coverage
describe("FilesController",() =>{
@@ -26,5 +36,30 @@ describe("FilesController",() =>{
expect(anObject.aFile.url).toEqual("http://an.url");
done();
})
});
it('should create a server log on failure', done => {
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(
() => done.fail('should not succeed'),
() => setImmediate(() => Parse.Promise.as('done'))
)
.then(() => logController.getLogs({ from: Date.now() - 500, 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();
expect(log1.level).toBe('error');
expect(log1.message).toBe('it failed');
const log2 = logs.pop();
expect(log2.level).toBe('error');
expect(log2.code).toBe(130);
expect(log2.message).toBe('Could not store file.');
done();
});
});
});
+45 -45
View File
@@ -1,12 +1,12 @@
'use strict';
var httpRequest = require("../src/cloud-code/httpRequest"),
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
bodyParser = require('body-parser'),
express = require("express");
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
bodyParser = require('body-parser'),
express = require("express");
var port = 13371;
var httpRequestServer = "http://localhost:"+port;
var httpRequestServer = "http://localhost:" + port;
var app = express();
app.use(bodyParser.json({ 'type': '*/*' }));
@@ -39,7 +39,7 @@ app.listen(13371);
describe("httpRequest", () => {
it("should do /hello", (done) => {
httpRequest({
url: httpRequestServer+"/hello"
url: httpRequestServer + "/hello"
}).then(function(httpResponse){
expect(httpResponse.status).toBe(200);
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
@@ -55,7 +55,7 @@ describe("httpRequest", () => {
it("should do /hello with callback and promises", (done) => {
var calls = 0;
httpRequest({
url: httpRequestServer+"/hello",
url: httpRequestServer + "/hello",
success: function() { calls++; },
error: function() { calls++; }
}).then(function(httpResponse){
@@ -74,7 +74,7 @@ describe("httpRequest", () => {
it("should do not follow redirects by default", (done) => {
httpRequest({
url: httpRequestServer+"/301"
url: httpRequestServer + "/301"
}).then(function(httpResponse){
expect(httpResponse.status).toBe(301);
done();
@@ -87,7 +87,7 @@ describe("httpRequest", () => {
it("should follow redirects when set", (done) => {
httpRequest({
url: httpRequestServer+"/301",
url: httpRequestServer + "/301",
followRedirects: true
}).then(function(httpResponse){
expect(httpResponse.status).toBe(200);
@@ -104,7 +104,7 @@ describe("httpRequest", () => {
it("should fail on 404", (done) => {
var calls = 0;
httpRequest({
url: httpRequestServer+"/404",
url: httpRequestServer + "/404",
success: function() {
calls++;
fail("should not succeed");
@@ -124,8 +124,8 @@ describe("httpRequest", () => {
it("should fail on 404", (done) => {
httpRequest({
url: httpRequestServer+"/404",
}).then(function(httpResponse){
url: httpRequestServer + "/404",
}).then(function(){
fail("should not succeed");
done();
}, function(httpResponse){
@@ -141,9 +141,9 @@ describe("httpRequest", () => {
var calls = 0;
httpRequest({
method: "POST",
url: httpRequestServer+"/echo",
url: httpRequestServer + "/echo",
body: {
foo: "bar"
foo: "bar"
},
headers: {
'Content-Type': 'application/json'
@@ -155,17 +155,17 @@ describe("httpRequest", () => {
expect(httpResponse.status).toBe(200);
expect(httpResponse.data).toEqual({foo: "bar"});
done();
}, function(httpResponse){
}, function(){
fail("should not fail");
done();
})
});
it("should encode a query string body by default", (done) => {
let options = {
const options = {
body: {"foo": "bar"},
}
let result = httpRequest.encodeBody(options);
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual('foo=bar');
expect(result.headers['Content-Type']).toEqual('application/x-www-form-urlencoded');
done();
@@ -173,30 +173,30 @@ describe("httpRequest", () => {
})
it("should encode a JSON body", (done) => {
let options = {
const options = {
body: {"foo": "bar"},
headers: {'Content-Type': 'application/json'}
}
let result = httpRequest.encodeBody(options);
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual('{"foo":"bar"}');
done();
})
it("should encode a www-form body", (done) => {
let options = {
it("should encode a www-form body", (done) => {
const options = {
body: {"foo": "bar", "bar": "baz"},
headers: {'cOntent-tYpe': 'application/x-www-form-urlencoded'}
}
let result = httpRequest.encodeBody(options);
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual("foo=bar&bar=baz");
done();
});
it("should not encode a wrong content type", (done) => {
let options = {
const options = {
body:{"foo": "bar", "bar": "baz"},
headers: {'cOntent-tYpe': 'mime/jpeg'}
}
let result = httpRequest.encodeBody(options);
const result = httpRequest.encodeBody(options);
expect(result.body).toEqual({"foo": "bar", "bar": "baz"});
done();
});
@@ -218,9 +218,9 @@ describe("httpRequest", () => {
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);
@@ -234,7 +234,7 @@ describe("httpRequest", () => {
it("should params string to query string", (done) => {
httpRequest({
url: httpRequestServer+"/qs",
url: httpRequestServer + "/qs",
params: "foo=bar&foo2=bar2"
}).then(function(httpResponse){
expect(httpResponse.status).toBe(200);
@@ -247,7 +247,7 @@ describe("httpRequest", () => {
});
it('should not crash with undefined body', () => {
let httpResponse = new HTTPResponse({});
const httpResponse = new HTTPResponse({});
expect(httpResponse.body).toBeUndefined();
expect(httpResponse.data).toBeUndefined();
expect(httpResponse.text).toBeUndefined();
@@ -255,24 +255,24 @@ describe("httpRequest", () => {
});
it('serialized httpResponse correctly with body string', () => {
let httpResponse = new HTTPResponse({}, 'hello');
const httpResponse = new HTTPResponse({}, 'hello');
expect(httpResponse.text).toBe('hello');
expect(httpResponse.data).toBe(undefined);
expect(httpResponse.body).toBe('hello');
let serialized = JSON.stringify(httpResponse);
let result = JSON.parse(serialized);
const serialized = JSON.stringify(httpResponse);
const result = JSON.parse(serialized);
expect(result.text).toBe('hello');
expect(result.data).toBe(undefined);
expect(result.body).toBe(undefined);
});
it('serialized httpResponse correctly with body object', () => {
let httpResponse = new HTTPResponse({}, {foo: "bar"});
let encodedResponse = Parse._encode(httpResponse);
let serialized = JSON.stringify(httpResponse);
let result = JSON.parse(serialized);
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'});
@@ -283,29 +283,29 @@ describe("httpRequest", () => {
});
it('serialized httpResponse correctly with body buffer string', () => {
let httpResponse = new HTTPResponse({}, new Buffer('hello'));
const httpResponse = new HTTPResponse({}, new Buffer('hello'));
expect(httpResponse.text).toBe('hello');
expect(httpResponse.data).toBe(undefined);
let serialized = JSON.stringify(httpResponse);
let result = JSON.parse(serialized);
const serialized = JSON.stringify(httpResponse);
const result = JSON.parse(serialized);
expect(result.text).toBe('hello');
expect(result.data).toBe(undefined);
});
it('serialized httpResponse correctly with body buffer JSON Object', () => {
let json = '{"foo":"bar"}';
let httpResponse = new HTTPResponse({}, new Buffer(json));
let serialized = JSON.stringify(httpResponse);
let result = JSON.parse(serialized);
const json = '{"foo":"bar"}';
const httpResponse = new HTTPResponse({}, new Buffer(json));
const serialized = JSON.stringify(httpResponse);
const result = JSON.parse(serialized);
expect(result.text).toEqual('{"foo":"bar"}');
expect(result.data).toEqual({foo: 'bar'});
});
it('serialized httpResponse with Parse._encode should be allright', () => {
let json = '{"foo":"bar"}';
let httpResponse = new HTTPResponse({}, new Buffer(json));
let encoded = Parse._encode(httpResponse);
const json = '{"foo":"bar"}';
const httpResponse = new HTTPResponse({}, new Buffer(json));
const encoded = Parse._encode(httpResponse);
let foundData, foundText, foundBody = false;
for(var key in encoded) {
if (key == 'data') {
+3 -3
View File
@@ -3,7 +3,7 @@ const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
describe('InMemoryCache', function() {
var BASE_TTL = {
ttl: 10
ttl: 100
};
var NO_EXPIRE_TTL = {
ttl: NaN
@@ -15,7 +15,7 @@ describe('InMemoryCache', function() {
function wait(sleep) {
return new Promise(function(resolve, reject) {
return new Promise(function(resolve) {
setTimeout(resolve, sleep);
})
}
@@ -28,7 +28,7 @@ describe('InMemoryCache', function() {
var value = cache.get(KEY);
expect(value).toEqual(VALUE);
wait(BASE_TTL.ttl * 5).then(() => {
wait(BASE_TTL.ttl * 2).then(() => {
value = cache.get(KEY)
expect(value).toEqual(null);
done();
+1 -3
View File
@@ -5,7 +5,7 @@ describe('InMemoryCacheAdapter', function() {
var VALUE = 'world';
function wait(sleep) {
return new Promise(function(resolve, reject) {
return new Promise(function(resolve) {
setTimeout(resolve, sleep);
})
}
@@ -15,8 +15,6 @@ describe('InMemoryCacheAdapter', function() {
ttl: NaN
});
var noop = () => {};
// Verify all methods return promises.
Promise.all([
cache.put(KEY, VALUE),
+62 -62
View File
@@ -28,18 +28,18 @@ describe('InstallationsRouter', () => {
var 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) => {
var results = res.response.results;
expect(results.length).toEqual(1);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
var results = res.response.results;
expect(results.length).toEqual(1);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('uses find condition from request.query', (done) => {
@@ -66,18 +66,18 @@ describe('InstallationsRouter', () => {
var 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) => {
var results = res.response.results;
expect(results.length).toEqual(1);
done();
}).catch((err) => {
jfail(err);
done();
});
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
var results = res.response.results;
expect(results.length).toEqual(1);
done();
}).catch((err) => {
jfail(err);
done();
});
});
it('query installations with limit = 0', (done) => {
@@ -100,21 +100,21 @@ describe('InstallationsRouter', () => {
info: {}
};
var config = new Config('test');
new Config('test');
var 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) => {
var response = res.response;
expect(response.results.length).toEqual(0);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
var response = res.response;
expect(response.results.length).toEqual(0);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('query installations with count = 1', done => {
@@ -139,18 +139,18 @@ describe('InstallationsRouter', () => {
var router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
.then(() => router.handleFind(request))
.then((res) => {
var response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(2);
done();
})
.catch(error => {
fail(JSON.stringify(error));
done();
})
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
.then(() => router.handleFind(request))
.then((res) => {
var response = res.response;
expect(response.results.length).toEqual(2);
expect(response.count).toEqual(2);
done();
})
.catch(error => {
fail(JSON.stringify(error));
done();
})
});
it('query installations with limit = 0 and count = 1', (done) => {
@@ -176,18 +176,18 @@ describe('InstallationsRouter', () => {
var 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) => {
var response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
.then(() => {
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
}).then(() => {
return router.handleFind(request);
}).then((res) => {
var response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
});
});
});
+198
View File
@@ -0,0 +1,198 @@
const rp = require('request-promise');
const defaultHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest'
}
const masterKeyHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest',
'X-Parse-Master-Key': 'test'
}
const defaultOptions = {
headers: defaultHeaders,
json: true
}
const masterKeyOptions = {
headers: masterKeyHeaders,
json: true
}
describe('JobSchedule', () => {
it('should create _JobSchedule with masterKey', (done) => {
const jobSchedule = new Parse.Object('_JobSchedule');
jobSchedule.set({
'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();
})
.then(() => {
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
})
.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'
}
}
});
rp.post(Parse.serverURL + '/cloud_code/jobs', options)
.then(done.fail)
.catch(done);
});
it('should update a job', (done) => {
Parse.Cloud.job('job1', () => {});
Parse.Cloud.job('job2', () => {});
const options = Object.assign({}, masterKeyOptions, {
body: {
job_schedule: {
jobName: 'job1'
}
}
});
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);
})
.then((res) => {
expect(res.length).toBe(1);
expect(res[0].jobName).toBe('job2');
})
.then(done)
.catch(done.fail);
});
it('should fail updating a job with an invalid name', (done) => {
Parse.Cloud.job('job1', () => {});
const options = Object.assign({}, masterKeyOptions, {
body: {
job_schedule: {
jobName: 'job1'
}
}
});
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(done.fail)
.catch(done);
});
it('should destroy a job', (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();
return rp.del(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, masterKeyOptions);
})
.then(() => {
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
})
.then((res) => {
expect(res.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, {
body: {
job_schedule: {
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);
})
.then((res) => {
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);
});
});
+10 -10
View File
@@ -22,9 +22,9 @@ describe('Logger', () => {
});
it('should have files transports', (done) => {
reconfigureServer().then(() => {
let transports = logging.logger.transports;
let transportKeys = Object.keys(transports);
reconfigureServer().then(() => {
const transports = logging.logger.transports;
const transportKeys = Object.keys(transports);
expect(transportKeys.length).toBe(3);
done();
});
@@ -33,9 +33,9 @@ describe('Logger', () => {
it('should disable files logs', (done) => {
reconfigureServer({
logsFolder: null
}).then(() => {
let transports = logging.logger.transports;
let transportKeys = Object.keys(transports);
}).then(() => {
const transports = logging.logger.transports;
const transportKeys = Object.keys(transports);
expect(transportKeys.length).toBe(1);
done();
});
@@ -47,16 +47,16 @@ describe('Logger', () => {
logsFolder: null,
jsonLogs: true,
silent: false
}).then(() => {
let spy = spyOn(process.stdout, 'write');
}).then(() => {
spyOn(process.stdout, 'write');
logging.logger.info('hi', {key: 'value'});
expect(process.stdout.write).toHaveBeenCalled();
var firstLog = process.stdout.write.calls.first().args[0];
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' })+'\n');
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' }) + '\n');
return reconfigureServer({
jsonLogs: false
});
}).then(() => {
}).then(() => {
done();
});
});
+27 -3
View File
@@ -12,7 +12,7 @@ describe('LoggerController', () => {
loggerController.getLogs(query).then(function(res) {
expect(res.length).not.toBe(0);
done();
}).catch((err) => {
}).catch((err) => {
jfail(err);
done();
})
@@ -74,7 +74,7 @@ describe('LoggerController', () => {
loggerController.getLogs(query).then(function(res) {
expect(res.length).toBe(0);
done();
}).catch((err) => {
}).catch((err) => {
jfail(err);
fail("should not fail");
done();
@@ -84,8 +84,32 @@ describe('LoggerController', () => {
it('should throw without an adapter', (done) => {
expect(() => {
var loggerController = new LoggerController();
new LoggerController();
}).toThrow();
done();
});
it('should replace implementations with verbose', (done) => {
const adapter = new WinstonLoggerAdapter();
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) => {
const adapter = new WinstonLoggerAdapter();
const logger = new LoggerController(adapter, null, { logLevel: 'error' });
spyOn(adapter, "log");
logger.warn('yo!');
logger.info('yo!');
logger.debug('yo!');
logger.verbose('yo!');
logger.silly('yo!');
expect(adapter.log).not.toHaveBeenCalled();
logger.error('error');
expect(adapter.log).toHaveBeenCalled();
done();
});
});
+283 -113
View File
@@ -3,121 +3,291 @@ var AppCache = require('../src/cache').AppCache;
describe('middlewares', () => {
var fakeReq, fakeRes;
var fakeReq, fakeRes;
beforeEach(() => {
fakeReq = {
originalUrl: 'http://example.com/parse/',
url: 'http://example.com/',
body: {
_ApplicationId: 'FakeAppId'
},
headers: {},
get: (key) => {
return fakeReq.headers[key.toLowerCase()]
}
};
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
AppCache.put(fakeReq.body._ApplicationId, {});
});
afterEach(() => {
AppCache.del(fakeReq.body._ApplicationId);
});
it('should use _ContentType if provided', (done) => {
expect(fakeReq.headers['content-type']).toEqual(undefined);
var 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()
});
});
it('should give invalid response when keys are configured but no key supplied', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should give invalid response when keys are configured but different key is supplied', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
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) => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: 'clientKey',
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed when no keys are configured and none supplied', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey'
});
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
const BodyParams = {
clientVersion: '_ClientVersion',
installationId: '_InstallationId',
sessionToken: '_SessionToken',
masterKey: '_MasterKey',
javascriptKey: '_JavaScriptKey'
beforeEach(() => {
fakeReq = {
originalUrl: 'http://example.com/parse/',
url: 'http://example.com/',
body: {
_ApplicationId: 'FakeAppId'
},
headers: {},
get: (key) => {
return fakeReq.headers[key.toLowerCase()]
}
};
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
AppCache.put(fakeReq.body._ApplicationId, {});
});
const BodyKeys = Object.keys(BodyParams);
afterEach(() => {
AppCache.del(fakeReq.body._ApplicationId);
});
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');
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) => {
expect(fakeReq.info[otherKey]).toEqual(undefined);
});
done();
});
});
it('should use _ContentType if provided', (done) => {
expect(fakeReq.headers['content-type']).toEqual(undefined);
var 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()
});
});
});
it('should give invalid response when keys are configured but no key supplied', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should give invalid response when keys are configured but different key is supplied', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
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) => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: 'clientKey',
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed when client key supplied but empty', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: '',
masterKey: 'masterKey',
restAPIKey: 'restAPIKey'
});
fakeReq.headers['x-parse-client-key'] = '';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed when no keys are configured and none supplied', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey'
});
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
const BodyParams = {
clientVersion: '_ClientVersion',
installationId: '_InstallationId',
sessionToken: '_SessionToken',
masterKey: '_MasterKey',
javascriptKey: '_JavaScriptKey'
};
const BodyKeys = Object.keys(BodyParams);
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');
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) => {
expect(fakeReq.info[otherKey]).toEqual(undefined);
});
done();
});
});
});
it('should not succeed if the ip does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
fakeReq.ip = 'ip3';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the ip does belong to masterKeyIps list', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
fakeReq.ip = 'ip1';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
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) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
fakeReq.connection = {remoteAddress : 'ip1'};
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
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) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
fakeReq.socket = {remoteAddress : 'ip1'};
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
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']
});
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) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
fakeReq.connection = { socket : {remoteAddress : 'ip1'}};
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should allow any ip to use masterKey if masterKeyIps is empty', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: []
});
fakeReq.ip = 'ip1';
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed if xff header does belong to masterKeyIps', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1']
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed if xff header with one ip does belong to masterKeyIps', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1']
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1';
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should not succeed if xff header does not belong to masterKeyIps', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip4']
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should not succeed if xff header is empty and masterKeyIps is set', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1']
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = '';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
});
+16 -16
View File
@@ -1,21 +1,21 @@
module.exports = options => {
if (!options) {
throw "Options were not provided"
}
let adapter = {
sendVerificationEmail: () => Promise.resolve(),
if (!options) {
throw "Options were not provided"
}
const adapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => Promise.resolve()
};
if (options.sendMail) {
adapter.sendMail = options.sendMail
}
if (options.sendPasswordResetEmail) {
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
}
if (options.sendVerificationEmail) {
adapter.sendVerificationEmail = options.sendVerificationEmail;
}
};
if (options.sendMail) {
adapter.sendMail = options.sendMail
}
if (options.sendPasswordResetEmail) {
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
}
if (options.sendVerificationEmail) {
adapter.sendVerificationEmail = options.sendVerificationEmail;
}
return adapter;
return adapter;
}
+9
View File
@@ -0,0 +1,9 @@
module.exports = function(options) {
return {
options: options,
send: function() {},
getValidPushTypes: function() {
return Object.keys(options.options);
}
};
};
+156 -71
View File
@@ -9,8 +9,8 @@ const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDataba
describe_only_db('mongo')('MongoStorageAdapter', () => {
beforeEach(done => {
new MongoStorageAdapter({ uri: databaseURI })
.deleteAllClasses()
.then(done, fail);
.deleteAllClasses()
.then(done, fail);
});
it('auto-escapes symbols in auth information', () => {
@@ -48,20 +48,57 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
it('stores objectId in _id', done => {
let adapter = new MongoStorageAdapter({ uri: databaseURI });
const adapter = new MongoStorageAdapter({ uri: databaseURI });
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', {}))
.then(results => {
expect(results.length).toEqual(1);
var obj = results[0];
expect(obj._id).toEqual('abcde');
expect(obj.objectId).toBeUndefined();
done();
.then(() => adapter._rawFind('Foo', {}))
.then(results => {
expect(results.length).toEqual(1);
var obj = results[0];
expect(obj._id).toEqual('abcde');
expect(obj.objectId).toBeUndefined();
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})` }))
.then(
() => done(),
(err) => {
done.fail(`maxTimeMS should not affect fast queries ${err}`);
}
);
})
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})` }))
.then(
() => {
done.fail('Find succeeded despite taking too long!');
},
(err) => {
expect(err.name).toEqual('MongoError');
expect(err.code).toEqual(50);
expect(err.message).toEqual('operation exceeded time limit');
done();
}
);
});
it('stores pointers with a _p_ prefix', (done) => {
let obj = {
const obj = {
objectId: 'bar',
aPointer: {
__type: 'Pointer',
@@ -69,52 +106,52 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
objectId: 'qwerty'
}
};
let adapter = new MongoStorageAdapter({ uri: databaseURI });
const adapter = new MongoStorageAdapter({ uri: databaseURI });
adapter.createObject('APointerDarkly', { fields: {
objectId: { type: 'String' },
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
}}, obj)
.then(() => adapter._rawFind('APointerDarkly', {}))
.then(results => {
expect(results.length).toEqual(1);
let output = results[0];
expect(typeof output._id).toEqual('string');
expect(typeof output._p_aPointer).toEqual('string');
expect(output._p_aPointer).toEqual('JustThePointer$qwerty');
expect(output.aPointer).toBeUndefined();
done();
});
.then(() => adapter._rawFind('APointerDarkly', {}))
.then(results => {
expect(results.length).toEqual(1);
const output = results[0];
expect(typeof output._id).toEqual('string');
expect(typeof output._p_aPointer).toEqual('string');
expect(output._p_aPointer).toEqual('JustThePointer$qwerty');
expect(output.aPointer).toBeUndefined();
done();
});
});
it('handles object and subdocument', done => {
let adapter = new MongoStorageAdapter({ uri: databaseURI });
let schema = { fields : { subdoc: { type: 'Object' } } };
let obj = { subdoc: {foo: 'bar', wu: 'tan'} };
const adapter = new MongoStorageAdapter({ uri: databaseURI });
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);
let mob = results[0];
expect(typeof mob.subdoc).toBe('object');
expect(mob.subdoc.foo).toBe('bar');
expect(mob.subdoc.wu).toBe('tan');
let obj = { 'subdoc.wu': 'clan' };
return adapter.findOneAndUpdate('MyClass', schema, {}, obj);
})
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
let mob = results[0];
expect(typeof mob.subdoc).toBe('object');
expect(mob.subdoc.foo).toBe('bar');
expect(mob.subdoc.wu).toBe('clan');
done();
});
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
const mob = results[0];
expect(typeof mob.subdoc).toBe('object');
expect(mob.subdoc.foo).toBe('bar');
expect(mob.subdoc.wu).toBe('tan');
const obj = { 'subdoc.wu': 'clan' };
return adapter.findOneAndUpdate('MyClass', schema, {}, obj);
})
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
const mob = results[0];
expect(typeof mob.subdoc).toBe('object');
expect(mob.subdoc.foo).toBe('bar');
expect(mob.subdoc.wu).toBe('clan');
done();
});
});
it('handles array, object, date', (done) => {
let adapter = new MongoStorageAdapter({ uri: databaseURI });
let obj = {
it('handles creating an array, object, date', (done) => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const obj = {
array: [1, 2, 3],
object: {foo: 'bar'},
date: {
@@ -122,34 +159,82 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
iso: '2016-05-26T20:55:01.154Z',
},
};
let schema = { fields: {
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);
let mob = results[0];
expect(mob.array instanceof Array).toBe(true);
expect(typeof mob.object).toBe('object');
expect(mob.date instanceof Date).toBe(true);
return adapter.find('MyClass', schema, {}, {});
})
.then(results => {
expect(results.length).toEqual(1);
let mob = results[0];
expect(mob.array instanceof Array).toBe(true);
expect(typeof mob.object).toBe('object');
expect(mob.date.__type).toBe('Date');
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
done();
})
.catch(error => {
console.log(error);
fail();
done();
});
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
const mob = results[0];
expect(mob.array instanceof Array).toBe(true);
expect(typeof mob.object).toBe('object');
expect(mob.date instanceof Date).toBe(true);
return adapter.find('MyClass', schema, {}, {});
})
.then(results => {
expect(results.length).toEqual(1);
const mob = results[0];
expect(mob.array instanceof Array).toBe(true);
expect(typeof mob.object).toBe('object');
expect(mob.date.__type).toBe('Date');
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
done();
})
.catch(error => {
console.log(error);
fail();
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' },
} };
adapter.createObject('MyClass', schema, {})
.then(() => adapter._rawFind('MyClass', {}))
.then(results => {
expect(results.length).toEqual(1);
const update = {
array: [1, 2, 3],
object: {foo: 'bar'},
date: {
__type: 'Date',
iso: '2016-05-26T20:55:01.154Z',
},
};
const query = {};
return adapter.findOneAndUpdate('MyClass', schema, query, update)
})
.then(results => {
const mob = results;
expect(mob.array instanceof Array).toBe(true);
expect(typeof mob.object).toBe('object');
expect(mob.date.__type).toBe('Date');
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
return adapter._rawFind('MyClass', {});
})
.then(results => {
expect(results.length).toEqual(1);
const mob = results[0];
expect(mob.array instanceof Array).toBe(true);
expect(typeof mob.object).toBe('object');
expect(mob.date instanceof Date).toBe(true);
done();
})
.catch(error => {
console.log(error);
fail();
done();
});
});
});
+73 -8
View File
@@ -1,9 +1,9 @@
// These tests are unit tests designed to only test transform.js.
"use strict";
let transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
let dd = require('deep-diff');
let mongodb = require('mongodb');
const transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
const dd = require('deep-diff');
const mongodb = require('mongodb');
describe('parseObjectToMongoObjectForCreate', () => {
it('a basic number', (done) => {
@@ -145,13 +145,25 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('geopoint', (done) => {
var input = {location: [180, -180]};
var input = {location: [45, -45]};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'GeoPoint' }},
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', longitude: 180, latitude: -180}
{__type: 'GeoPoint', longitude: 45, latitude: -45}
);
done();
});
it('polygon', (done) => {
var input = {location: { type: 'Polygon', coordinates: [[[45, -45],[45, -45]]]}};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'Polygon' }},
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'Polygon', coordinates: [[45, -45],[45, -45]]}
);
done();
});
@@ -179,7 +191,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('untransforms objects containing nested special keys', done => {
let input = {array: [{
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: {
@@ -190,7 +202,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
},
regularKey: "some data",
}]}
let output = transform.mongoObjectToParseObject(null, input, {
const output = transform.mongoObjectToParseObject(null, input, {
fields: { array: { type: 'Array' }},
});
expect(dd(output, input)).toEqual(undefined);
@@ -236,6 +248,19 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('removes Relation types', (done) => {
var input = {
aRelation: { __type: 'Relation', className: 'Stuff' },
};
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {
aRelation: { __type: 'Relation', className: 'Stuff' },
},
});
expect(output).toEqual({});
done();
});
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
var input = {
_rperm: ['*'],
@@ -265,7 +290,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('untransforms mongodb number types', (done) => {
it('untransforms mongodb number types', (done) => {
var input = {
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
double: new mongodb.Double(Number.MAX_VALUE)
@@ -281,4 +306,44 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('Date object where iso attribute is of type Date', (done) => {
var input = {
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
}
var output = transform.mongoObjectToParseObject(null, input, {
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) => {
var input = {
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
}
var output = transform.mongoObjectToParseObject(null, input, {
fields : {
ts : { type : 'Date' }
}
});
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
done();
});
});
describe('transformUpdate', () => {
it('removes Relation types', (done) => {
var input = {
aRelation: { __type: 'Relation', className: 'Stuff' },
};
var output = transform.transformUpdate(null, input, {
fields: {
aRelation: { __type: 'Relation', className: 'Stuff' },
},
});
expect(output).toEqual({});
done();
});
});
-329
View File
@@ -1,329 +0,0 @@
var OAuth = require("../src/authDataManager/OAuth1Client");
var request = require('request');
var Config = require("../src/Config");
var defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
describe('OAuth', function() {
it("Nonce should have right length", (done) => {
jequal(OAuth.nonce().length, 30);
done();
});
it("Should properly build parameter string", (done) => {
var 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) => {
var string = OAuth.buildParameterString()
jequal(string, "");
done();
});
it("Should properly build signature string", (done) => {
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
done();
});
it("Should properly generate request signature", (done) => {
var request = {
host: "dummy.com",
path: "path"
};
var oauth_params = {
oauth_timestamp: 123450000,
oauth_nonce: "AAAAAAAAAAAAAAAAA",
oauth_consumer_key: "hello",
oauth_token: "token"
};
var consumer_secret = "world";
var 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) => {
var options = {
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"
}
};
var path = "path";
var method = "get";
var oauthClient = new OAuth(options);
var req = oauthClient.buildRequest(method, path, {"query": "param"});
jequal(req.host, options.host);
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"')
done();
});
function validateCannotAuthenticateError(data, done) {
jequal(typeof data, "object");
jequal(typeof data.errors, "object");
var errors = data.errors;
jequal(typeof errors[0], "object");
// Cannot authenticate error
jequal(errors[0].code, 32);
done();
}
it("Should fail a GET request", (done) => {
var options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
var path = "/1.1/help/configuration.json";
var params = {"lang": "en"};
var oauthClient = new OAuth(options);
oauthClient.get(path, params).then(function(data){
validateCannotAuthenticateError(data, done);
})
});
it("Should fail a POST request", (done) => {
var options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
var body = {
lang: "en"
};
var path = "/1.1/account/settings.json";
var oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(data){
validateCannotAuthenticateError(data, done);
})
});
it("Should fail a request", (done) => {
var options = {
host: "localhost",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
var body = {
lang: "en"
};
var path = "/";
var oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(data){
jequal(false, true);
done();
}).catch(function(){
jequal(true, true);
done();
})
});
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
it("Should validate structure of "+providerName, (done) => {
var provider = require("../src/authDataManager/"+providerName);
jequal(typeof provider.validateAuthData, "function");
jequal(typeof provider.validateAppId, "function");
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
jequal(provider.validateAppId("app", "key", {}).constructor, Promise.prototype.constructor);
done();
});
});
var getMockMyOauthProvider = function() {
return {
authData: {
id: "12345",
access_token: "12345",
expiration_date: new Date().toJSON(),
},
shouldError: false,
loggedOut: false,
synchronizedUserId: null,
synchronizedAuthToken: null,
synchronizedExpiration: null,
authenticate: function(options) {
if (this.shouldError) {
options.error(this, "An error occurred");
} else if (this.shouldCancel) {
options.error(this, null);
} else {
options.success(this, this.authData);
}
},
restoreAuthentication: function(authData) {
if (!authData) {
this.synchronizedUserId = null;
this.synchronizedAuthToken = null;
this.synchronizedExpiration = null;
return true;
}
this.synchronizedUserId = authData.id;
this.synchronizedAuthToken = authData.access_token;
this.synchronizedExpiration = authData.expiration_date;
return true;
},
getAuthType: function() {
return "myoauth";
},
deauthenticate: function() {
this.loggedOut = true;
this.restoreAuthentication(null);
}
};
};
var ExtendedUser = Parse.User.extend({
extended: function() {
return true;
}
});
var createOAuthUser = function(callback) {
var jsonBody = {
authData: {
myoauth: getMockMyOauthProvider().authData
}
};
var options = {
headers: {'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Installation-Id': 'yolo',
'Content-Type': 'application/json' },
url: 'http://localhost:8378/1/users',
body: JSON.stringify(jsonBody)
};
return request.post(options, callback);
}
it("should create user with REST API", done => {
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
ok(b.sessionToken);
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
var sessionToken = b.sessionToken;
var q = new Parse.Query("_Session");
q.equalTo('sessionToken', sessionToken);
q.first({useMasterKey: true}).then((res) => {
if (!res) {
fail('should not fail fetching the session');
done();
return;
}
expect(res.get("installationId")).toEqual('yolo');
done();
}).fail((err) => {
fail('should not fail fetching the session');
done();
})
});
});
it("should only create a single user with REST API", (done) => {
var objectId;
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
objectId = b.objectId;
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
expect(b.objectId).toBe(objectId);
done();
});
});
});
it("unlink and link with custom provider", (done) => {
var provider = getMockMyOauthProvider();
Parse.User._registerAuthenticationProvider(provider);
Parse.User._logInWith("myoauth", {
success: function(model) {
ok(model instanceof Parse.User, "Model should be a Parse.User");
strictEqual(Parse.User.current(), model);
ok(model.extended(), "Should have used the subclass.");
strictEqual(provider.authData.id, provider.synchronizedUserId);
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
model._unlinkFrom("myoauth", {
success: function(model) {
ok(!model._isLinked("myoauth"),
"User should not be linked to myoauth");
ok(!provider.synchronizedUserId, "User id should be cleared");
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
ok(!provider.synchronizedExpiration,
"Expiration should be cleared");
// make sure the auth data is properly deleted
var config = new Config(Parse.applicationId);
config.database.adapter.find('_User', {
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
}, { objectId: model.id }, {})
.then(res => {
expect(res.length).toBe(1);
expect(res[0]._auth_data_myoauth).toBeUndefined();
expect(res[0]._auth_data_myoauth).not.toBeNull();
model._linkWith("myoauth", {
success: function(model) {
ok(provider.synchronizedUserId, "User id should have a value");
ok(provider.synchronizedAuthToken,
"Auth token should have a value");
ok(provider.synchronizedExpiration,
"Expiration should have a value");
ok(model._isLinked("myoauth"),
"User should be linked to myoauth");
done();
},
error: function(model, error) {
ok(false, "linking again should succeed");
done();
}
});
});
},
error: function(model, error) {
ok(false, "unlinking should succeed");
done();
}
});
},
error: function(model, error) {
ok(false, "linking should have worked");
done();
}
});
});
})
+146
View File
@@ -0,0 +1,146 @@
var OAuth = require("../src/Adapters/Auth/OAuth1Client");
describe('OAuth', function() {
it("Nonce should have right length", (done) => {
jequal(OAuth.nonce().length, 30);
done();
});
it("Should properly build parameter string", (done) => {
var 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) => {
var string = OAuth.buildParameterString()
jequal(string, "");
done();
});
it("Should properly build signature string", (done) => {
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
done();
});
it("Should properly generate request signature", (done) => {
var request = {
host: "dummy.com",
path: "path"
};
var oauth_params = {
oauth_timestamp: 123450000,
oauth_nonce: "AAAAAAAAAAAAAAAAA",
oauth_consumer_key: "hello",
oauth_token: "token"
};
var consumer_secret = "world";
var 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) => {
var options = {
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"
}
};
var path = "path";
var method = "get";
var oauthClient = new OAuth(options);
var req = oauthClient.buildRequest(method, path, {"query": "param"});
jequal(req.host, options.host);
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"')
done();
});
function validateCannotAuthenticateError(data, done) {
jequal(typeof data, "object");
jequal(typeof data.errors, "object");
var errors = data.errors;
jequal(typeof errors[0], "object");
// Cannot authenticate error
jequal(errors[0].code, 32);
done();
}
it("Should fail a GET request", (done) => {
var options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
var path = "/1.1/help/configuration.json";
var params = {"lang": "en"};
var oauthClient = new OAuth(options);
oauthClient.get(path, params).then(function(data){
validateCannotAuthenticateError(data, done);
})
});
it("Should fail a POST request", (done) => {
var options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
var body = {
lang: "en"
};
var path = "/1.1/account/settings.json";
var oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(data){
validateCannotAuthenticateError(data, done);
})
});
it("Should fail a request", (done) => {
var options = {
host: "localhost",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
var body = {
lang: "en"
};
var path = "/";
var oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(){
jequal(false, true);
done();
}).catch(function(){
jequal(true, true);
done();
})
});
it("Should fail with missing options", (done) => {
var options = undefined;
try {
new OAuth(options);
} catch (error) {
jequal(error.message, 'No options passed to OAuth');
done();
}
});
});
+148 -121
View File
@@ -1,23 +1,33 @@
'use strict';
let request = require('request');
const request = require('request');
const delayPromise = (delay) => {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
describe('Parse.Push', () => {
var setup = function() {
var sendToInstallationSpy = jasmine.createSpy();
var pushAdapter = {
send: function(body, installations) {
var badge = body.data.badge;
let promises = installations.map((installation) => {
const promises = installations.map((installation) => {
sendToInstallationSpy(installation);
if (installation.deviceType == "ios") {
expect(installation.badge).toEqual(badge);
expect(installation.originalBadge+1).toEqual(installation.badge);
expect(installation.originalBadge + 1).toEqual(installation.badge);
} else {
expect(installation.badge).toBeUndefined();
}
return Promise.resolve({
err: null,
deviceType: installation.deviceType,
result: true
device: installation,
transmitted: true
})
});
return Promise.all(promises);
@@ -35,128 +45,58 @@ describe('Parse.Push', () => {
adapter: pushAdapter
}
})
.then(() => {
var installations = [];
while(installations.length != 10) {
var 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);
}).catch((err) => {
console.error(err);
})
.then(() => {
var installations = [];
while(installations.length != 10) {
var 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);
})
.then(() => {
return {
sendToInstallationSpy,
};
})
.catch((err) => {
console.error(err);
throw err;
})
}
it('should properly send push', (done) => {
return setup().then(() => {
return setup().then(({ sendToInstallationSpy }) => {
return Parse.Push.send({
where: {
deviceType: 'ios'
},
data: {
badge: 'Increment',
alert: 'Hello world!'
}
}, {useMasterKey: true})
})
.then(() => {
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) => {
}).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(() => {
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(() => {
request.get({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
},
}, (error, response, body) => {
expect(body.error).toEqual('unauthorized');
done();
});
}).catch((err) => {
jfail(err);
done();
});
});
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(() => {
request.get({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
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) {
jfail(e);
}
done();
});
}).catch((err) => {
jfail(err);
done();
});
});
it('should throw error if missing push configuration', done => {
reconfigureServer({push: null})
.then(() => {
return setup().then(() => {
return Parse.Push.send({
where: {
deviceType: 'ios'
@@ -166,14 +106,101 @@ describe('Parse.Push', () => {
alert: 'Hello world!'
}
}, {useMasterKey: true})
}).then((response) => {
fail('should not succeed');
}, (err) => {
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
}).then(() => {
return delayPromise(500);
}).then(() => {
done();
}).catch((err) => {
}).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(() => delayPromise(500))
.then(() => {
request.get({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
},
}, (error, response, body) => {
expect(body.error).toEqual('unauthorized');
done();
});
}).catch((err) => {
jfail(err);
done();
});
});
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(() => delayPromise(500)) // put a delay as we keep writing
.then(() => {
request.get({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
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) {
jfail(e);
}
done();
});
}).catch((err) => {
jfail(err);
done();
});
});
it('should throw error if missing push configuration', done => {
reconfigureServer({push: null})
.then(() => {
return Parse.Push.send({
where: {
deviceType: 'ios'
},
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) => {
jfail(err);
done();
});
});
});
+468 -435
View File
File diff suppressed because it is too large Load Diff
+381 -383
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -20,7 +20,7 @@ describe('ParseCloudCodePublisher', function() {
it('can initialize', function() {
var config = {}
var publisher = new ParseCloudCodePublisher(config);
new ParseCloudCodePublisher(config);
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
@@ -32,7 +32,7 @@ describe('ParseCloudCodePublisher', function() {
var request = {};
publisher.onCloudCodeAfterSave(request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith('afterSave', request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterSave', request);
});
it('can handle cloud code afterDelete request', function() {
@@ -41,7 +41,7 @@ describe('ParseCloudCodePublisher', function() {
var request = {};
publisher.onCloudCodeAfterDelete(request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith('afterDelete', request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterDelete', request);
});
it('can handle cloud code request', function() {
+294 -8
View File
@@ -113,7 +113,7 @@ describe('Parse.File testing', () => {
'X-Parse-Master-Key': 'test'
},
url: 'http://localhost:8378/1/files/' + b.name
}, (error, response, body) => {
}, (error, response) => {
expect(error).toBe(null);
expect(response.statusCode).toEqual(200);
request.get({
@@ -122,7 +122,7 @@ describe('Parse.File testing', () => {
'X-Parse-REST-API-Key': 'rest'
},
url: b.url
}, (error, response, body) => {
}, (error, response) => {
expect(error).toBe(null);
try {
expect(response.statusCode).toEqual(404);
@@ -370,7 +370,7 @@ describe('Parse.File testing', () => {
object.save({
file: file
}, expectSuccess({
success: function(obj) {
success: function() {
ok(object.toJSON().file.url);
done();
}
@@ -391,7 +391,7 @@ describe('Parse.File testing', () => {
expect(error).toBe(null);
var b = JSON.parse(body);
expect(b.name).toMatch(/\.html$/);
request.get(b.url, (error, response, body) => {
request.get(b.url, (error, response) => {
if (!response) {
fail('response should be set');
return done();
@@ -563,7 +563,7 @@ describe('Parse.File testing', () => {
var query = new Parse.Query('FileTest');
return query.first();
}).then(result => {
let fileAgain = result.get('file');
const fileAgain = result.get('file');
expect(fileAgain.url()).toMatch(/123.txt$/);
done();
}).catch((e) => {
@@ -572,10 +572,10 @@ describe('Parse.File testing', () => {
});
});
it('return with publicServerURL when provided', done => {
it('return with publicServerURL when provided', done => {
reconfigureServer({
publicServerURL: 'https://mydomain/parse'
}).then(() => {
}).then(() => {
var file = {
__type: 'File',
name: '123.txt'
@@ -587,7 +587,7 @@ describe('Parse.File testing', () => {
var query = new Parse.Query('FileTest');
return query.first();
}).then(result => {
let fileAgain = result.get('file');
const fileAgain = result.get('file');
expect(fileAgain.url().indexOf('https://mydomain/parse')).toBe(0);
done();
}).catch((e) => {
@@ -595,4 +595,290 @@ describe('Parse.File testing', () => {
done();
});
});
it('fails to upload an empty file', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: '',
}, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toBe(400);
expect(body).toEqual('{"code":130,"error":"Invalid file upload."}');
done();
});
});
it('fails to upload without a file name', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/',
body: 'yolo',
}, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toBe(400);
expect(body).toEqual('{"code":122,"error":"Filename not provided."}');
done();
});
});
it('fails to upload without a file name', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/',
body: 'yolo',
}, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toBe(400);
expect(body).toEqual('{"code":122,"error":"Filename not provided."}');
done();
});
});
it('fails to delete an unkown file', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Master-Key': 'test'
};
request.delete({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
}, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toBe(400);
expect(body).toEqual('{"code":153,"error":"Could not delete file."}');
done();
});
});
describe_only_db('mongo')('Gridstore Range tests', () => {
it('supports range requests', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=0-5'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body).toEqual('argle ');
done();
});
});
});
it('supports small range requests', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=0-2'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body).toEqual('arg');
done();
});
});
});
// See specs https://www.greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#byte.ranges
it('supports getting one byte', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=2-2'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body).toEqual('g');
done();
});
});
});
it('supports getting last n bytes', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: 'something different',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=-4'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body.length).toBe(4);
expect(body).toEqual('rent');
done();
});
});
});
it('supports getting first n bytes', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: 'something different',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=10-'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body).toEqual('different');
done();
});
});
});
function repeat(string, count) {
var s = string;
while (count > 0) {
s += string;
count--;
}
return s;
}
it('supports large range requests', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: repeat('argle bargle', 100)
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=13-240'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body.length).toEqual(228);
expect(body.indexOf('rgle barglea')).toBe(0);
done();
});
});
});
it('fails to stream unknown file', done => {
request.get({ url: 'http://localhost:8378/1/files/test/file.txt', headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=13-240'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toBe(404);
expect(body).toEqual('File not found.');
done();
});
});
});
// Because GridStore is not loaded on PG, those are perfect
// for fallback tests
describe_only_db('postgres')('Default Range tests', () => {
it('fallback to regular request', done => {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/file.txt',
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
request.get({ url: b.url, headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Range': 'bytes=0-5'
} }, (error, response, body) => {
expect(error).toBe(null);
expect(body).toEqual('argle bargle');
done();
});
});
});
});
});
+317 -21
View File
@@ -1,9 +1,11 @@
// This is a port of the test suite:
// hungry/js/test/parse_geo_point_test.js
const rp = require('request-promise');
var TestObject = Parse.Object.extend('TestObject');
describe('Parse.GeoPoint testing', () => {
it('geo point roundtrip', (done) => {
var point = new Parse.GeoPoint(44.0, -11.0);
var obj = new TestObject();
@@ -26,6 +28,47 @@ describe('Parse.GeoPoint testing', () => {
});
});
it('update geopoint', (done) => {
const oldPoint = new Parse.GeoPoint(44.0, -11.0);
const newPoint = new Parse.GeoPoint(24.0, 19.0);
const obj = new TestObject();
obj.set('location', oldPoint);
obj.save().then(() => {
obj.set('location', newPoint);
return obj.save();
}).then(() => {
var query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const point = result.get('location');
equal(point.latitude, newPoint.latitude);
equal(point.longitude, newPoint.longitude);
done();
});
});
it('has the correct __type field in the json response', done => {
var point = new Parse.GeoPoint(44.0, -11.0);
var obj = new TestObject();
obj.set('location', point);
obj.set('name', 'Zhoul')
obj.save(null, {
success: (obj) => {
Parse.Cloud.httpRequest({
url: 'http://localhost:8378/1/classes/TestObject/' + obj.id,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
}
}).then(response => {
equal(response.data.location.__type, 'GeoPoint');
done();
})
}
})
});
it('geo point exception two fields', (done) => {
var point = new Parse.GeoPoint(20, 20);
var obj = new TestObject();
@@ -76,7 +119,7 @@ describe('Parse.GeoPoint testing', () => {
obj.set('index', i);
objects.push(obj);
});
Parse.Object.saveAll(objects).then((list) => {
Parse.Object.saveAll(objects).then(() => {
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(1.0, -1.0);
query.withinRadians('location', point, 3.14);
@@ -99,7 +142,7 @@ describe('Parse.GeoPoint testing', () => {
obj.set('index', i);
objects.push(obj);
});
Parse.Object.saveAll(objects, function(list) {
Parse.Object.saveAll(objects, function() {
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(1.0, -1.0);
query.withinRadians('location', point, 3.14 * 0.5);
@@ -123,7 +166,7 @@ describe('Parse.GeoPoint testing', () => {
obj.set('index', i);
objects.push(obj);
});
Parse.Object.saveAll(objects, function(list) {
Parse.Object.saveAll(objects, function() {
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(1.0, -1.0);
query.withinRadians('location', point, 3.14 * 0.25);
@@ -154,7 +197,7 @@ describe('Parse.GeoPoint testing', () => {
};
it('geo max distance in km everywhere', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
// Honolulu is 4300 km away from SFO on a sphere ;)
@@ -169,7 +212,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in km california', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinKilometers('location', sfo, 3700.0);
@@ -185,7 +228,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in km bay area', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinKilometers('location', sfo, 100.0);
@@ -200,7 +243,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in km mid peninsula', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinKilometers('location', sfo, 10.0);
@@ -214,7 +257,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in miles everywhere', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinMiles('location', sfo, 2600.0);
@@ -228,7 +271,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in miles california', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinMiles('location', sfo, 2200.0);
@@ -244,7 +287,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in miles bay area', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
// 100km is 62 miles...
@@ -260,7 +303,7 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance in miles mid peninsula', (done) => {
makeSomeGeoPoints(function(list) {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinMiles('location', sfo, 10.0);
@@ -273,19 +316,36 @@ describe('Parse.GeoPoint testing', () => {
});
});
it('works with geobox queries', (done) => {
var inSF = new Parse.GeoPoint(37.75, -122.4);
var southwestOfSF = new Parse.GeoPoint(37.708813, -122.526398);
var northeastOfSF = new Parse.GeoPoint(37.822802, -122.373962);
var object = new TestObject();
object.set('point', inSF);
object.save().then(() => {
it('returns nearest location', (done) => {
makeSomeGeoPoints(function() {
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinGeoBox('point', southwestOfSF, northeastOfSF);
query.near('location', sfo);
query.find({
success: function(results) {
equal(results[0].get('name'), 'San Francisco');
equal(results[1].get('name'), 'Sacramento');
done();
}
});
});
});
it('works with geobox queries', (done) => {
const inbound = new Parse.GeoPoint(1.5, 1.5);
const onbound = new Parse.GeoPoint(10, 10);
const outbound = new Parse.GeoPoint(20, 20);
const obj1 = new Parse.Object('TestObject', {location: inbound});
const obj2 = new Parse.Object('TestObject', {location: onbound});
const obj3 = new Parse.Object('TestObject', {location: outbound});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const sw = new Parse.GeoPoint(0, 0);
const ne = new Parse.GeoPoint(10, 10);
const query = new Parse.Query(TestObject);
query.withinGeoBox('location', sw, ne);
return query.find();
}).then((results) => {
equal(results.length, 1);
equal(results.length, 2);
done();
});
});
@@ -332,4 +392,240 @@ describe('Parse.GeoPoint testing', () => {
}
});
});
it('equalTo geopoint', (done) => {
var point = new Parse.GeoPoint(44.0, -11.0);
var obj = new TestObject();
obj.set('location', point);
obj.save().then(() => {
const query = new Parse.Query(TestObject);
query.equalTo('location', point);
return query.find();
}).then((results) => {
equal(results.length, 1);
const loc = results[0].get('location');
equal(loc.latitude, point.latitude);
equal(loc.longitude, point.longitude);
done();
});
});
it('supports withinPolygon open path', (done) => {
const inbound = new Parse.GeoPoint(1.5, 1.5);
const onbound = new Parse.GeoPoint(10, 10);
const outbound = new Parse.GeoPoint(20, 20);
const obj1 = new Parse.Object('Polygon', {location: inbound});
const obj2 = new Parse.Object('Polygon', {location: onbound});
const obj3 = new Parse.Object('Polygon', {location: outbound});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoWithin: {
$polygon: [
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
{ __type: 'GeoPoint', latitude: 0, longitude: 10 },
{ __type: 'GeoPoint', latitude: 10, longitude: 10 },
{ __type: 'GeoPoint', latitude: 10, longitude: 0 }
]
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
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);
});
it('supports withinPolygon closed path', (done) => {
const inbound = new Parse.GeoPoint(1.5, 1.5);
const onbound = new Parse.GeoPoint(10, 10);
const outbound = new Parse.GeoPoint(20, 20);
const obj1 = new Parse.Object('Polygon', {location: inbound});
const obj2 = new Parse.Object('Polygon', {location: onbound});
const obj3 = new Parse.Object('Polygon', {location: outbound});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoWithin: {
$polygon: [
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
{ __type: 'GeoPoint', latitude: 0, longitude: 10 },
{ __type: 'GeoPoint', latitude: 10, longitude: 10 },
{ __type: 'GeoPoint', latitude: 10, longitude: 0 },
{ __type: 'GeoPoint', latitude: 0, longitude: 0 }
]
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
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);
});
it('invalid input withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: 1234
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
it('invalid geoPoint withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: [
{}
]
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
it('invalid latitude withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: [
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
{ __type: 'GeoPoint', latitude: 181, longitude: 0 },
{ __type: 'GeoPoint', latitude: 0, longitude: 0 }
]
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(1);
done();
});
});
it('invalid longitude withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: [
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
{ __type: 'GeoPoint', latitude: 0, longitude: 181 },
{ __type: 'GeoPoint', latitude: 0, longitude: 0 }
]
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(1);
done();
});
});
it('minimum 3 points withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: []
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/Polygon',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(107);
done();
});
});
});
+49 -7
View File
@@ -1,13 +1,12 @@
'use strict';
var request = require('request');
var Parse = require('parse/node').Parse;
let Config = require('../src/Config');
const Config = require('../src/Config');
describe('a GlobalConfig', () => {
beforeEach(done => {
let config = new Config('test');
let query = on_db('mongo', () => {
const config = new Config('test');
const query = on_db('mongo', () => {
// Legacy is with an int...
return { objectId: 1 };
}, () => {
@@ -18,7 +17,7 @@ describe('a GlobalConfig', () => {
{ fields: { objectId: { type: 'Number' }, params: {type: 'Object'}} },
query,
{ params: { companies: ['US', 'DK'] } }
).then(done, (err) => {
).then(done, (err) => {
jfail(err);
done();
});
@@ -57,6 +56,49 @@ describe('a GlobalConfig', () => {
});
});
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);
expect(body.result).toEqual(true);
Parse.Config.get().then((res) => {
const file = res.get('file');
expect(file.name()).toBe('name');
expect(file.url()).toBe('http://url');
done();
});
});
});
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);
expect(body.result).toEqual(true);
Parse.Config.get().then((res) => {
const point = res.get('point');
expect(point.latitude).toBe(10);
expect(point.longitude).toBe(-20);
done();
});
});
});
it('properly handles delete op', (done) => {
request.put({
url : 'http://localhost:8378/1/config',
@@ -105,7 +147,7 @@ describe('a GlobalConfig', () => {
});
it('failed getting config when it is missing', (done) => {
let config = new Config('test');
const config = new Config('test');
config.database.adapter.deleteObjectsByQuery(
'_GlobalConfig',
{ fields: { params: { __type: 'String' } } },
@@ -123,7 +165,7 @@ describe('a GlobalConfig', () => {
expect(body.params).toEqual({});
done();
});
}).catch((e) => {
}).catch((e) => {
jfail(e);
done();
});
+417 -416
View File
@@ -7,147 +7,149 @@ var express = require("express");
var bodyParser = require('body-parser');
var port = 12345;
var hookServerURL = "http://localhost:"+port;
let AppCache = require('../src/cache').AppCache;
var hookServerURL = "http://localhost:" + port;
const AppCache = require('../src/cache').AppCache;
var app = express();
app.use(bodyParser.json({ 'type': '*/*' }))
app.listen(12345);
describe('Hooks', () => {
it("should have no hooks registered", (done) => {
Parse.Hooks.getFunctions().then((res) => {
expect(res.constructor).toBe(Array.prototype.constructor);
done();
}, (err) => {
jfail(err);
done();
});
});
it("should have no hooks registered", (done) => {
Parse.Hooks.getFunctions().then((res) => {
expect(res.constructor).toBe(Array.prototype.constructor);
done();
}, (err) => {
jfail(err);
done();
});
});
it("should have no triggers registered", (done) => {
Parse.Hooks.getTriggers().then( (res) => {
expect(res.constructor).toBe(Array.prototype.constructor);
done();
}, (err) => {
jfail(err);
done();
});
});
it("should have no triggers registered", (done) => {
Parse.Hooks.getTriggers().then((res) => {
expect(res.constructor).toBe(Array.prototype.constructor);
done();
}, (err) => {
jfail(err);
done();
});
});
it("should CRUD a function registration", (done) => {
// Create
Parse.Hooks.createFunction("My-Test-Function", "http://someurl")
.then(response => {
expect(response.functionName).toBe("My-Test-Function");
expect(response.url).toBe("http://someurl")
// Find
return Parse.Hooks.getFunction("My-Test-Function")
}).then(response => {
expect(response.objectId).toBeUndefined();
expect(response.url).toBe("http://someurl");
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
})
.then((res) => {
expect(res.objectId).toBeUndefined();
expect(res.functionName).toBe("My-Test-Function");
expect(res.url).toBe("http://anotherurl")
// delete
return Parse.Hooks.removeFunction("My-Test-Function")
})
.then((res) => {
.then(response => {
expect(response.functionName).toBe("My-Test-Function");
expect(response.url).toBe("http://someurl")
// Find
return Parse.Hooks.getFunction("My-Test-Function")
}).then(response => {
expect(response.objectId).toBeUndefined();
expect(response.url).toBe("http://someurl");
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
})
.then((res) => {
expect(res.objectId).toBeUndefined();
expect(res.functionName).toBe("My-Test-Function");
expect(res.url).toBe("http://anotherurl")
// delete
return Parse.Hooks.removeFunction("My-Test-Function")
})
.then(() => {
// Find again! but should be deleted
return Parse.Hooks.getFunction("My-Test-Function")
.then(res => {
fail("Failed to delete hook")
fail(res)
return Parse.Hooks.getFunction("My-Test-Function")
.then(res => {
fail("Failed to delete hook")
fail(res)
done();
return Promise.resolve();
}, (err) => {
expect(err.code).toBe(143);
expect(err.message).toBe("no function named: My-Test-Function is defined")
done();
return Promise.resolve();
})
})
.catch(error => {
jfail(error);
done();
return Promise.resolve();
}, (err) => {
expect(err.code).toBe(143);
expect(err.message).toBe("no function named: My-Test-Function is defined")
done();
return Promise.resolve();
})
})
.catch(error => {
jfail(error);
done();
})
});
it("should CRUD a trigger registration", (done) => {
// Create
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
expect(res.className).toBe("MyClass");
expect(res.triggerName).toBe("beforeDelete");
expect(res.url).toBe("http://someurl")
// Find
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
}, (err) => {
fail(err);
done();
}).then((res) => {
expect(res).not.toBe(null);
expect(res).not.toBe(undefined);
expect(res.objectId).toBeUndefined();
expect(res.url).toBe("http://someurl");
// delete
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
}, (err) => {
jfail(err);
done();
}).then((res) => {
expect(res.className).toBe("MyClass");
expect(res.url).toBe("http://anotherurl")
expect(res.objectId).toBeUndefined();
// Create
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
expect(res.className).toBe("MyClass");
expect(res.triggerName).toBe("beforeDelete");
expect(res.url).toBe("http://someurl")
// Find
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
}, (err) => {
fail(err);
done();
}).then((res) => {
expect(res).not.toBe(null);
expect(res).not.toBe(undefined);
expect(res.objectId).toBeUndefined();
expect(res.url).toBe("http://someurl");
// delete
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
}, (err) => {
jfail(err);
done();
}).then((res) => {
expect(res.className).toBe("MyClass");
expect(res.url).toBe("http://anotherurl")
expect(res.objectId).toBeUndefined();
return Parse.Hooks.removeTrigger("MyClass","beforeDelete");
}, (err) => {
jfail(err);
done();
}).then((res) => {
// Find again! but should be deleted
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
}, (err) => {
jfail(err);
done();
}).then(function(){
fail("should not succeed");
done();
}, (err) => {
return Parse.Hooks.removeTrigger("MyClass","beforeDelete");
}, (err) => {
jfail(err);
done();
}).then(() => {
// Find again! but should be deleted
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
}, (err) => {
jfail(err);
done();
}).then(function(){
fail("should not succeed");
done();
}, (err) => {
if (err) {
expect(err).not.toBe(null);
expect(err).not.toBe(undefined);
expect(err.code).toBe(143);
expect(err.message).toBe("class MyClass does not exist")
} else {
} else {
fail('should have errored');
}
done();
});
});
}
done();
});
});
it("should fail to register hooks without Master Key", (done) => {
request.post(Parse.serverURL+"/hooks/functions", {
headers: {
"X-Parse-Application-Id": Parse.applicationId,
"X-Parse-REST-API-Key": Parse.restKey,
},
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
}, (err, res, body) => {
body = JSON.parse(body);
expect(body.error).toBe("unauthorized");
done();
})
});
it("should fail to register hooks without Master Key", (done) => {
request.post(Parse.serverURL + "/hooks/functions", {
headers: {
"X-Parse-Application-Id": Parse.applicationId,
"X-Parse-REST-API-Key": Parse.restKey,
},
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
}, (err, res, body) => {
body = JSON.parse(body);
expect(body.error).toBe("unauthorized");
done();
})
});
it("should fail trying to create two times the same function", (done) => {
Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => {
it("should fail trying to create two times the same function", (done) => {
Parse.Hooks.createFunction("my_new_function", "http://url.com")
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
.then(() => {
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
}, () => {
fail("should create a new function");
}).then( () => {
}).then(() => {
fail("should not be able to create the same function");
}, (err) => {
expect(err).not.toBe(undefined);
@@ -163,185 +165,185 @@ describe('Hooks', () => {
jfail(err);
done();
})
});
});
it("should fail trying to create two times the same trigger", (done) => {
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => {
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
}, () => {
fail("should create a new trigger");
}).then( () => {
fail("should not be able to create the same trigger");
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('class MyClass already has trigger beforeSave')
}
return Parse.Hooks.removeTrigger("MyClass", "beforeSave");
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
})
});
it("should fail trying to create two times the same trigger", (done) => {
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then(() => {
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
}, () => {
fail("should create a new trigger");
}).then(() => {
fail("should not be able to create the same trigger");
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('class MyClass already has trigger beforeSave')
}
return Parse.Hooks.removeTrigger("MyClass", "beforeSave");
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
})
});
it("should fail trying to update a function that don't exist", (done) => {
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => {
fail("Should not succeed")
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
}
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
}).then( (res) => {
fail("the function should not exist");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
}
done();
});
});
it("should fail trying to update a function that don't exist", (done) => {
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then(() => {
fail("Should not succeed")
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
}
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
}).then(() => {
fail("the function should not exist");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
}
done();
});
});
it("should fail trying to update a trigger that don't exist", (done) => {
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => {
fail("Should not succeed")
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('class AClassName does not exist');
}
return Parse.Hooks.getTrigger("AClassName","beforeSave")
}).then( (res) => {
fail("the function should not exist");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('class AClassName does not exist');
}
done();
});
});
it("should fail trying to update a trigger that don't exist", (done) => {
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then(() => {
fail("Should not succeed")
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('class AClassName does not exist');
}
return Parse.Hooks.getTrigger("AClassName","beforeSave")
}).then(() => {
fail("the function should not exist");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('class AClassName does not exist');
}
done();
});
});
it("should fail trying to create a malformed function", (done) => {
Parse.Hooks.createFunction("MyFunction").then( (res) => {
fail(res);
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.error).toBe("invalid hook declaration");
}
done();
});
});
it("should fail trying to create a malformed function", (done) => {
Parse.Hooks.createFunction("MyFunction").then((res) => {
fail(res);
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.error).toBe("invalid hook declaration");
}
done();
});
});
it("should fail trying to create a malformed function (REST)", (done) => {
request.post(Parse.serverURL+"/hooks/functions", {
headers: {
"X-Parse-Application-Id": Parse.applicationId,
"X-Parse-Master-Key": Parse.masterKey,
},
body: JSON.stringify({ functionName: "SomeFunction"})
}, (err, res, body) => {
body = JSON.parse(body);
expect(body.error).toBe("invalid hook declaration");
expect(body.code).toBe(143);
done();
})
});
it("should fail trying to create a malformed function (REST)", (done) => {
request.post(Parse.serverURL + "/hooks/functions", {
headers: {
"X-Parse-Application-Id": Parse.applicationId,
"X-Parse-Master-Key": Parse.masterKey,
},
body: JSON.stringify({ functionName: "SomeFunction"})
}, (err, res, body) => {
body = JSON.parse(body);
expect(body.error).toBe("invalid hook declaration");
expect(body.code).toBe(143);
done();
})
});
it("should create hooks and properly preload them", (done) => {
it("should create hooks and properly preload them", (done) => {
var promises = [];
for (var i = 0; i<5; i++) {
promises.push(Parse.Hooks.createTrigger("MyClass"+i, "beforeSave", "http://url.com/beforeSave/"+i));
promises.push(Parse.Hooks.createFunction("AFunction"+i, "http://url.com/function"+i));
}
var promises = [];
for (var i = 0; i < 5; i++) {
promises.push(Parse.Hooks.createTrigger("MyClass" + i, "beforeSave", "http://url.com/beforeSave/" + i));
promises.push(Parse.Hooks.createFunction("AFunction" + i, "http://url.com/function" + i));
}
Parse.Promise.when(promises).then(function(results){
for (var i=0; i<5; i++) {
// Delete everything from memory, as the server just started
triggers.removeTrigger("beforeSave", "MyClass"+i, Parse.applicationId);
triggers.removeFunction("AFunction"+i, Parse.applicationId);
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).toBeUndefined();
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).toBeUndefined();
}
const hooksController = new HooksController(Parse.applicationId, AppCache.get('test').databaseController);
return hooksController.load()
}, (err) => {
jfail(err);
fail('Should properly create all hooks');
done();
}).then(function() {
for (var i=0; i<5; i++) {
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).not.toBeUndefined();
}
done();
}, (err) => {
jfail(err);
fail('should properly load all hooks');
done();
})
});
Parse.Promise.when(promises).then(function(){
for (var i = 0; i < 5; i++) {
// Delete everything from memory, as the server just started
triggers.removeTrigger("beforeSave", "MyClass" + i, Parse.applicationId);
triggers.removeFunction("AFunction" + i, Parse.applicationId);
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).toBeUndefined();
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).toBeUndefined();
}
const hooksController = new HooksController(Parse.applicationId, AppCache.get('test').databaseController);
return hooksController.load()
}, (err) => {
jfail(err);
fail('Should properly create all hooks');
done();
}).then(function() {
for (var i = 0; i < 5; i++) {
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).not.toBeUndefined();
}
done();
}, (err) => {
jfail(err);
fail('should properly load all hooks');
done();
})
});
it("should run the function on the test server", (done) => {
it("should run the function on the test server", (done) => {
app.post("/SomeFunction", function(req, res) {
res.json({success:"OK!"});
});
app.post("/SomeFunction", function(req, res) {
res.json({success:"OK!"});
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(res){
expect(res).toBe("OK!");
done();
}, (err) => {
jfail(err);
fail("Should not fail calling a function");
done();
});
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunction").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(res){
expect(res).toBe("OK!");
done();
}, (err) => {
jfail(err);
fail("Should not fail calling a function");
done();
});
});
it("should run the function on the test server", (done) => {
it("should run the function on the test server", (done) => {
app.post("/SomeFunctionError", function(req, res) {
res.json({error: {code: 1337, error: "hacking that one!"}});
});
// The function is deleted as the DB is dropped between calls
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunctionError").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(res){
fail("Should not succeed calling that function");
done();
}, (err) => {
app.post("/SomeFunctionError", function(req, res) {
res.json({error: {code: 1337, error: "hacking that one!"}});
});
// The function is deleted as the DB is dropped between calls
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunctionError").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function() {
fail("Should not succeed calling that function");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
@@ -350,148 +352,147 @@ describe('Hooks', () => {
expect(err.message.error).toEqual("hacking that one!");
}
done();
});
});
});
});
it("should provide X-Parse-Webhook-Key when defined", (done) => {
app.post("/ExpectingKey", function(req, res) {
if (req.get('X-Parse-Webhook-Key') === 'hook') {
res.json({success: "correct key provided"});
} else {
res.json({error: "incorrect key provided"});
}
});
it("should provide X-Parse-Webhook-Key when defined", (done) => {
app.post("/ExpectingKey", function(req, res) {
if (req.get('X-Parse-Webhook-Key') === 'hook') {
res.json({success: "correct key provided"});
} else {
res.json({error: "incorrect key provided"});
}
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKey").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(res){
expect(res).toBe("correct key provided");
done();
}, (err) => {
jfail(err);
fail("Should not fail calling a function");
done();
});
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKey").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(res){
expect(res).toBe("correct key provided");
done();
}, (err) => {
jfail(err);
fail("Should not fail calling a function");
done();
});
});
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
reconfigureServer({ webhookKey: undefined })
.then(() => {
app.post("/ExpectingKeyAlso", function(req, res) {
if (req.get('X-Parse-Webhook-Key') === 'hook') {
res.json({success: "correct key provided"});
} else {
res.json({error: "incorrect key provided"});
}
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKeyAlso").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(res){
fail("Should not succeed calling that function");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(141);
expect(err.message).toEqual("incorrect key provided");
}
done();
});
});
});
it("should run the beforeSave hook on the test server", (done) => {
var triggerCount = 0;
app.post("/BeforeSaveSome", function(req, res) {
triggerCount++;
var object = req.body.object;
object.hello = "world";
// Would need parse cloud express to set much more
// But this should override the key upon return
res.json({success: object});
});
// The function is delete as the DB is dropped between calls
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave" ,hookServerURL+"/BeforeSaveSome").then(function(){
const obj = new Parse.Object("SomeRandomObject");
return obj.save();
}).then(function(res) {
expect(triggerCount).toBe(1);
return res.fetch();
}).then(function(res) {
expect(res.get("hello")).toEqual("world");
done();
}).fail((err) => {
jfail(err);
fail("Should not fail creating a function");
done();
});
});
it("beforeSave hooks should correctly handle responses containing entire object", (done) => {
app.post("/BeforeSaveSome2", function(req, res) {
var object = Parse.Object.fromJSON(req.body.object);
object.set('hello', "world");
res.json({success: object});
});
Parse.Hooks.createTrigger("SomeRandomObject2", "beforeSave" ,hookServerURL+"/BeforeSaveSome2").then(function(){
const obj = new Parse.Object("SomeRandomObject2");
return obj.save();
}).then(function(res) {
return res.save();
}).then(function(res) {
expect(res.get("hello")).toEqual("world");
done();
}).fail((err) => {
fail(`Should not fail: ${JSON.stringify(err)}`);
done();
});
});
it("should run the afterSave hook on the test server", (done) => {
var triggerCount = 0;
var newObjectId;
app.post("/AfterSaveSome", function(req, res) {
triggerCount++;
var obj = new Parse.Object("AnotherObject");
obj.set("foo", "bar");
obj.save().then(function(obj){
newObjectId = obj.id;
res.json({success: {}});
})
});
// The function is delete as the DB is dropped between calls
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave" ,hookServerURL+"/AfterSaveSome").then(function(){
const obj = new Parse.Object("SomeRandomObject");
return obj.save();
}).then(function(res){
var promise = new Parse.Promise();
// Wait a bit here as it's an after save
setTimeout(function(){
expect(triggerCount).toBe(1);
var q = new Parse.Query("AnotherObject");
q.get(newObjectId).then(function(r){
promise.resolve(r);
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
reconfigureServer({ webhookKey: undefined })
.then(() => {
app.post("/ExpectingKeyAlso", function(req, res) {
if (req.get('X-Parse-Webhook-Key') === 'hook') {
res.json({success: "correct key provided"});
} else {
res.json({error: "incorrect key provided"});
}
});
}, 300)
return promise;
}).then(function(res){
expect(res.get("foo")).toEqual("bar");
done();
}).fail((err) => {
jfail(err);
fail("Should not fail creating a function");
done();
});
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKeyAlso").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
fail("Should not fail creating a function");
done();
}).then(function(){
fail("Should not succeed calling that function");
done();
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(141);
expect(err.message).toEqual("incorrect key provided");
}
done();
});
});
});
it("should run the beforeSave hook on the test server", (done) => {
var triggerCount = 0;
app.post("/BeforeSaveSome", function(req, res) {
triggerCount++;
var object = req.body.object;
object.hello = "world";
// Would need parse cloud express to set much more
// But this should override the key upon return
res.json({success: object});
});
// The function is deleted as the DB is dropped between calls
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave", hookServerURL + "/BeforeSaveSome").then(function () {
const obj = new Parse.Object("SomeRandomObject");
return obj.save();
}).then(function(res) {
expect(triggerCount).toBe(1);
return res.fetch();
}).then(function(res) {
expect(res.get("hello")).toEqual("world");
done();
}).fail((err) => {
jfail(err);
fail("Should not fail creating a function");
done();
});
});
it("beforeSave hooks should correctly handle responses containing entire object", (done) => {
app.post("/BeforeSaveSome2", function(req, res) {
var object = Parse.Object.fromJSON(req.body.object);
object.set('hello', "world");
res.json({success: object});
});
Parse.Hooks.createTrigger("SomeRandomObject2", "beforeSave", hookServerURL + "/BeforeSaveSome2").then(function(){
const obj = new Parse.Object("SomeRandomObject2");
return obj.save();
}).then(function(res) {
return res.save();
}).then(function(res) {
expect(res.get("hello")).toEqual("world");
done();
}).fail((err) => {
fail(`Should not fail: ${JSON.stringify(err)}`);
done();
});
});
it("should run the afterSave hook on the test server", (done) => {
var triggerCount = 0;
var newObjectId;
app.post("/AfterSaveSome", function(req, res) {
triggerCount++;
var obj = new Parse.Object("AnotherObject");
obj.set("foo", "bar");
obj.save().then(function(obj){
newObjectId = obj.id;
res.json({success: {}});
})
});
// The function is deleted as the DB is dropped between calls
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave", hookServerURL + "/AfterSaveSome").then(function(){
const obj = new Parse.Object("SomeRandomObject");
return obj.save();
}).then(function() {
var promise = new Parse.Promise();
// Wait a bit here as it's an after save
setTimeout(() => {
expect(triggerCount).toBe(1);
new Parse.Query("AnotherObject")
.get(newObjectId)
.then((r) => promise.resolve(r));
}, 500);
return promise;
}).then(function(res){
expect(res.get("foo")).toEqual("bar");
done();
}).fail((err) => {
jfail(err);
fail("Should not fail creating a function");
done();
});
});
});
File diff suppressed because it is too large Load Diff
+125 -18
View File
@@ -12,7 +12,7 @@ describe('ParseLiveQueryServer', function() {
var mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer');
jasmine.mockLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer', mockParseWebSocketServer);
// Mock Client
var mockClient = function() {
var mockClient = function(id, socket, hasMasterKey) {
this.pushConnect = jasmine.createSpy('pushConnect');
this.pushSubscribe = jasmine.createSpy('pushSubscribe');
this.pushUnsubscribe = jasmine.createSpy('pushUnsubscribe');
@@ -24,6 +24,7 @@ describe('ParseLiveQueryServer', function() {
this.addSubscriptionInfo = jasmine.createSpy('addSubscriptionInfo');
this.getSubscriptionInfo = jasmine.createSpy('getSubscriptionInfo');
this.deleteSubscriptionInfo = jasmine.createSpy('deleteSubscriptionInfo');
this.hasMasterKey = hasMasterKey;
}
mockClient.pushError = jasmine.createSpy('pushError');
jasmine.mockLibrary('../src/LiveQuery/Client', 'Client', mockClient);
@@ -80,7 +81,7 @@ describe('ParseLiveQueryServer', function() {
var httpServer = {};
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, httpServer);
expect(parseLiveQueryServer.clientId).toBe(0);
expect(parseLiveQueryServer.clientId).toBeUndefined();
expect(parseLiveQueryServer.clients.size).toBe(0);
expect(parseLiveQueryServer.subscriptions.size).toBe(0);
});
@@ -93,9 +94,11 @@ describe('ParseLiveQueryServer', function() {
parseLiveQueryServer._validateKeys = jasmine.createSpy('validateKeys').and.returnValue(true);
parseLiveQueryServer._handleConnect(parseWebSocket);
expect(parseLiveQueryServer.clientId).toBe(1);
expect(parseWebSocket.clientId).toBe(0);
var client = parseLiveQueryServer.clients.get(0);
const clientKeys = parseLiveQueryServer.clients.keys();
expect(parseLiveQueryServer.clients.size).toBe(1);
const firstKey = clientKeys.next().value;
expect(parseWebSocket.clientId).toBe(firstKey);
var client = parseLiveQueryServer.clients.get(firstKey);
expect(client).not.toBeNull();
// Make sure we send connect response to the client
expect(client.pushConnect).toHaveBeenCalled();
@@ -159,7 +162,7 @@ describe('ParseLiveQueryServer', function() {
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
// Add two mock clients
var clientId = 1;
var client = addMockClient(parseLiveQueryServer, clientId);
addMockClient(parseLiveQueryServer, clientId);
var clientIdAgain = 2;
var clientAgain = addMockClient(parseLiveQueryServer, clientIdAgain);
// Add subscription for mock client 1
@@ -232,7 +235,7 @@ describe('ParseLiveQueryServer', function() {
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
// Add mock client
var clientId = 1;
var client = addMockClient(parseLiveQueryServer, clientId);
addMockClient(parseLiveQueryServer, clientId);
// Handle unsubscribe command
var parseWebSocket = {
clientId: 1
@@ -274,7 +277,7 @@ describe('ParseLiveQueryServer', function() {
expect(subscriptions.size).toBe(0);
});
it('can set connect command message handler for a parseWebSocket', function() {
it('can set connect command message handler for a parseWebSocket', function() {
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
// Register mock connect/subscribe/unsubscribe handler for the server
parseLiveQueryServer._handleConnect = jasmine.createSpy('_handleSubscribe');
@@ -358,7 +361,7 @@ describe('ParseLiveQueryServer', function() {
expect(args[0]).toBe(parseWebSocket);
expect(JSON.stringify(args[1])).toBe(updateRequest);
expect(parseLiveQueryServer._handleUnsubscribe).toHaveBeenCalled();
let unsubArgs = parseLiveQueryServer._handleUnsubscribe.calls.mostRecent().args;
const unsubArgs = parseLiveQueryServer._handleUnsubscribe.calls.mostRecent().args;
expect(unsubArgs.length).toBe(3);
expect(unsubArgs[2]).toBe(false);
expect(parseLiveQueryServer._handleSubscribe).toHaveBeenCalled();
@@ -393,6 +396,28 @@ describe('ParseLiveQueryServer', function() {
parseWebSocket.emit('disconnect');
});
it('can forward event to cloud code', function() {
const cloudCodeHandler = {
handler: () => {}
}
const spy = spyOn(cloudCodeHandler, 'handler').and.callThrough();
Parse.Cloud.onLiveQueryEvent(cloudCodeHandler.handler);
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
var EventEmitter = require('events');
var parseWebSocket = new EventEmitter();
parseWebSocket.clientId = 1;
// Register message handlers for the parseWebSocket
parseLiveQueryServer._onConnect(parseWebSocket);
// Make sure we do not crash
// Trigger disconnect event
parseWebSocket.emit('disconnect');
expect(spy).toHaveBeenCalled();
// call for ws_connect, another for ws_disconnect
expect(spy.calls.count()).toBe(2);
});
// TODO: Test server can set disconnect command message handler for a parseWebSocket
it('has no subscription and can handle object delete command', function() {
@@ -452,7 +477,7 @@ describe('ParseLiveQueryServer', function() {
key: 'value',
className: testClassName
});
// Make mock message
// Make mock message
var message = {
currentParseObject: parseObject
};
@@ -533,7 +558,7 @@ describe('ParseLiveQueryServer', function() {
// In order to mimic a enter, we need original match return false
// and the current match return true
var counter = 0;
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
parseLiveQueryServer._matchesSubscription = function(parseObject){
if (!parseObject) {
return false;
}
@@ -567,7 +592,7 @@ describe('ParseLiveQueryServer', function() {
var requestId = 2;
addMockSubscription(parseLiveQueryServer, clientId, requestId);
// Mock _matchesSubscription to return matching
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
parseLiveQueryServer._matchesSubscription = function(parseObject){
if (!parseObject) {
return false;
}
@@ -603,7 +628,7 @@ describe('ParseLiveQueryServer', function() {
// In order to mimic a leave, we need original match return true
// and the current match return false
var counter = 0;
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
parseLiveQueryServer._matchesSubscription = function(parseObject){
if (!parseObject) {
return false;
}
@@ -637,7 +662,7 @@ describe('ParseLiveQueryServer', function() {
var requestId = 2;
addMockSubscription(parseLiveQueryServer, clientId, requestId);
// Mock _matchesSubscription to return matching
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
parseLiveQueryServer._matchesSubscription = function(parseObject){
if (!parseObject) {
return false;
}
@@ -746,7 +771,6 @@ describe('ParseLiveQueryServer', function() {
};
var requestId = 0;
var isChecked = false;
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
expect(isMatched).toBe(false);
done();
@@ -916,7 +940,7 @@ describe('ParseLiveQueryServer', function() {
spyOn(Parse, "Query").and.callFake(function(){
return {
equalTo(relation, value) {
equalTo() {
// Nothing to do here
},
find() {
@@ -929,7 +953,7 @@ describe('ParseLiveQueryServer', function() {
}
}
});
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
expect(isMatched).toBe(false);
done();
@@ -952,7 +976,7 @@ describe('ParseLiveQueryServer', function() {
spyOn(Parse, "Query").and.callFake(function(){
return {
equalTo(relation, value) {
equalTo() {
// Nothing to do here
},
find() {
@@ -1019,6 +1043,89 @@ describe('ParseLiveQueryServer', function() {
expect(parseLiveQueryServer._validateKeys(request, parseLiveQueryServer.keyPairs)).toBeTruthy();
});
it('can validate client has master key when valid', function() {
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
keyPairs: {
masterKey: 'test'
}
});
var request = {
masterKey: 'test'
};
expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).toBeTruthy();
});
it('can validate client doesn\'t have master key when invalid', function() {
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
keyPairs: {
masterKey: 'test'
}
});
var request = {
masterKey: 'notValid'
};
expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).not.toBeTruthy();
});
it('can validate client doesn\'t have master key when not provided', function() {
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
keyPairs: {
masterKey: 'test'
}
});
expect(parseLiveQueryServer._hasMasterKey({}, parseLiveQueryServer.keyPairs)).not.toBeTruthy();
});
it('can validate client doesn\'t have master key when validKeyPairs is empty', function() {
var parseLiveQueryServer = new ParseLiveQueryServer({}, {});
var request = {
masterKey: 'test'
};
expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).not.toBeTruthy();
});
it('will match non-public ACL when client has master key', function(done){
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
var acl = new Parse.ACL();
acl.setPublicReadAccess(false);
var client = {
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
}),
hasMasterKey: true
};
var requestId = 0;
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
expect(isMatched).toBe(true);
done();
});
});
it('won\'t match non-public ACL when client has no master key', function(done){
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
var acl = new Parse.ACL();
acl.setPublicReadAccess(false);
var client = {
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
}),
hasMasterKey: false
};
var requestId = 0;
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
expect(isMatched).toBe(false);
done();
});
});
afterEach(function(){
jasmine.restoreLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer');
jasmine.restoreLibrary('../src/LiveQuery/Client', 'Client');
+80 -85
View File
@@ -14,7 +14,7 @@
describe('Parse.Object testing', () => {
it("create", function(done) {
create({ "test" : "test" }, function(model, response) {
create({ "test" : "test" }, function(model) {
ok(model.id, "Should have an objectId set");
equal(model.get("test"), "test", "Should have the right attribute");
done();
@@ -22,11 +22,11 @@ describe('Parse.Object testing', () => {
});
it("update", function(done) {
create({ "test" : "test" }, function(model, response) {
create({ "test" : "test" }, function(model) {
var t2 = new TestObject({ objectId: model.id });
t2.set("test", "changed");
t2.save(null, {
success: function(model, response) {
success: function(model) {
equal(model.get("test"), "changed", "Update should have succeeded");
done();
}
@@ -72,10 +72,10 @@ describe('Parse.Object testing', () => {
});
it("get", function(done) {
create({ "test" : "test" }, function(model, response) {
create({ "test" : "test" }, function(model) {
var t2 = new TestObject({ objectId: model.id });
t2.fetch({
success: function(model2, response) {
success: function(model2) {
equal(model2.get("test"), "test", "Update should have succeeded");
ok(model2.id);
equal(model2.id, model.id, "Ids should match");
@@ -144,16 +144,16 @@ describe('Parse.Object testing', () => {
});
it("save adds no data keys (other than createdAt and updatedAt)",
function(done) {
var object = new TestObject();
object.save(null, {
success: function() {
var keys = Object.keys(object.attributes).sort();
equal(keys.length, 2);
done();
}
});
});
function(done) {
var object = new TestObject();
object.save(null, {
success: function() {
var keys = Object.keys(object.attributes).sort();
equal(keys.length, 2);
done();
}
});
});
it("recursive save", function(done) {
var item = new Item();
@@ -269,20 +269,19 @@ describe('Parse.Object testing', () => {
});
it_exclude_dbs(['postgres'])("can set null", function(done) {
var errored = false;
var obj = new Parse.Object("TestObject");
obj.set("foo", null);
obj.save(null, {
success: function(obj) {
on_db('mongo', () => {
on_db('mongo', () => {
equal(obj.get("foo"), null);
});
on_db('postgres', () => {
on_db('postgres', () => {
fail('should not succeed');
});
done();
},
error: function(obj, error) {
error: function() {
fail('should not fail');
done();
}
@@ -323,11 +322,11 @@ describe('Parse.Object testing', () => {
it("invalid class name", function(done) {
var item = new Parse.Object("Foo^bar");
item.save(null, {
success: function(item) {
success: function() {
ok(false, "The name should have been invalid.");
done();
},
error: function(item, error) {
error: function() {
// Because the class name is invalid, the router will not be able to route
// it, so it will actually return a -1 error code.
// equal(error.code, Parse.Error.INVALID_CLASS_NAME);
@@ -339,14 +338,13 @@ describe('Parse.Object testing', () => {
it("invalid key name", function(done) {
var item = new Parse.Object("Item");
ok(!item.set({"foo^bar": "baz"}),
'Item should not be updated with invalid key.');
'Item should not be updated with invalid key.');
item.save({ "foo^bar": "baz" }).then(fail, done);
});
it("invalid __type", function(done) {
var item = new Parse.Object("Item");
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes'];
var Error = Parse.Error;
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes', 'Polygon'];
var tests = types.map(type => {
var test = new Parse.Object("Item");
test.set('foo', {
@@ -368,7 +366,7 @@ describe('Parse.Object testing', () => {
"foo": {
__type: "IvalidName"
}
}).then(fail, err => next(0));
}).then(fail, () => next(0));
});
it("simple field deletion", function(done) {
@@ -598,8 +596,8 @@ describe('Parse.Object testing', () => {
var query = new Parse.Query('X');
return query.get(x1.id);
}).then((x3) => {
let stuff = x3.get('stuff');
let expected = [1, 2, 4];
const stuff = x3.get('stuff');
const expected = [1, 2, 4];
expect(stuff.length).toBe(expected.length);
for (var i of stuff) {
expect(expected.indexOf(i) >= 0).toBe(true);
@@ -609,7 +607,7 @@ describe('Parse.Object testing', () => {
on_db('mongo', () => {
jfail(error);
});
on_db('postgres', () => {
on_db('postgres', () => {
expect(error.message).toEqual("Postgres does not support AddUnique operator.");
});
done();
@@ -630,12 +628,12 @@ describe('Parse.Object testing', () => {
var query = new Parse.Query('X');
return query.get(x1.id);
}).then((x3) => {
let stuff = x3.get('stuff');
let target = [1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}];
const stuff = x3.get('stuff');
const target = [1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}];
expect(stuff.length).toEqual(target.length);
let found = 0;
for (let thing in target) {
for (let st in stuff) {
for (const thing in target) {
for (const st in stuff) {
if (st == thing) {
found++;
}
@@ -688,7 +686,7 @@ describe('Parse.Object testing', () => {
done();
},
error: function(object, error) {
error: function() {
ok(false, "This should have saved.");
done();
}
@@ -1079,7 +1077,7 @@ describe('Parse.Object testing', () => {
parent.set('children', [child1, child2]);
parent.save(null, {
success: function(parent) {
success: function() {
var query = new Parse.Query(Child);
query.ascending('name');
query.find({
@@ -1199,8 +1197,7 @@ describe('Parse.Object testing', () => {
});
it("toJSON saved object", function(done) {
var _ = Parse._;
create({ "foo" : "bar" }, function(model, response) {
create({ "foo" : "bar" }, function(model) {
var objJSON = model.toJSON();
ok(objJSON.foo, "expected json to contain key 'foo'");
ok(objJSON.objectId, "expected json to contain key 'objectId'");
@@ -1274,7 +1271,7 @@ describe('Parse.Object testing', () => {
return bryan.save({
meal: "tomatoes"
});
}, function(error) {
}, function() {
ok(false, "Save should have succeeded.");
}).then(function() {
ok(false, "Save should have failed.");
@@ -1331,7 +1328,7 @@ describe('Parse.Object testing', () => {
});
});
it_exclude_dbs(['postgres'])("bytes work", function(done) {
it("bytes work", function(done) {
Parse.Promise.as().then(function() {
var obj = new TestObject();
obj.set("bytes", { __type: "Bytes", base64: "ZnJveW8=" });
@@ -1393,7 +1390,7 @@ describe('Parse.Object testing', () => {
}
equal(itemsAgain.length, numItems, "Should get the array back");
itemsAgain.forEach(function(item, i) {
var newValue = i*2;
var newValue = i * 2;
item.set("x", newValue);
});
return Parse.Object.saveAll(itemsAgain);
@@ -1401,9 +1398,9 @@ describe('Parse.Object testing', () => {
return Parse.Object.fetchAll(items);
}).then(function(fetchedItemsAgain) {
equal(fetchedItemsAgain.length, numItems,
"Number of items fetched should not change");
"Number of items fetched should not change");
fetchedItemsAgain.forEach(function(item, i) {
equal(item.get("x"), i*2);
equal(item.get("x"), i * 2);
});
done();
});
@@ -1460,7 +1457,7 @@ describe('Parse.Object testing', () => {
}
equal(itemsAgain.length, numItems, "Should get the array back");
itemsAgain.forEach(function(item, i) {
var newValue = i*2;
var newValue = i * 2;
item.set("x", newValue);
});
return Parse.Object.saveAll(itemsAgain);
@@ -1468,13 +1465,13 @@ describe('Parse.Object testing', () => {
return Parse.Object.fetchAll(items, {
success: function(fetchedItemsAgain) {
equal(fetchedItemsAgain.length, numItems,
"Number of items fetched should not change");
"Number of items fetched should not change");
fetchedItemsAgain.forEach(function(item, i) {
equal(item.get("x"), i*2);
equal(item.get("x"), i * 2);
});
done();
},
error: function(error) {
error: function() {
ok(false, "Failed to fetchAll");
done();
}
@@ -1502,13 +1499,11 @@ describe('Parse.Object testing', () => {
it("fetchAll error on unsaved object", function(done) {
var unsavedObjectArray = [new TestObject()];
Parse.Object.fetchAll(unsavedObjectArray,
expectError(Parse.Error.MISSING_OBJECT_ID, done));
expectError(Parse.Error.MISSING_OBJECT_ID, done));
});
it("fetchAll error on deleted object", function(done) {
var numItems = 11;
var container = new Container();
var subContainer = new Container();
var items = [];
for (var i = 0; i < numItems; i++) {
var item = new Item();
@@ -1586,14 +1581,14 @@ describe('Parse.Object testing', () => {
return;
}
itemsAgain.forEach(function(item, i) {
item.set("x", i*2);
item.set("x", i * 2);
});
return Parse.Object.saveAll(itemsAgain);
}).then(function() {
return Parse.Object.fetchAllIfNeeded(items);
}).then(function(fetchedItems) {
equal(fetchedItems.length, numItems,
"Number of items should not change");
"Number of items should not change");
fetchedItems.forEach(function(item, i) {
equal(item.get("x"), i);
});
@@ -1624,7 +1619,7 @@ describe('Parse.Object testing', () => {
return;
}
itemsAgain.forEach(function(item, i) {
item.set("x", i*2);
item.set("x", i * 2);
});
return Parse.Object.saveAll(itemsAgain);
}).then(function() {
@@ -1632,14 +1627,14 @@ describe('Parse.Object testing', () => {
return Parse.Object.fetchAllIfNeeded(items, {
success: function(fetchedItems) {
equal(fetchedItems.length, numItems,
"Number of items should not change");
"Number of items should not change");
fetchedItems.forEach(function(item, j) {
equal(item.get("x"), j);
});
done();
},
error: function(error) {
error: function() {
ok(false, "Failed to fetchAll");
done();
}
@@ -1685,7 +1680,7 @@ describe('Parse.Object testing', () => {
});
equal(User1.className, "_User",
"className is rewritten by default");
"className is rewritten by default");
Parse.User.allowCustomUserClass(true);
equal(Parse.CoreManager.get('PERFORM_USER_REWRITE'), false);
@@ -1694,7 +1689,7 @@ describe('Parse.Object testing', () => {
});
equal(User2.className, "User",
"className is not rewritten when allowCustomUserClass(true)");
"className is not rewritten when allowCustomUserClass(true)");
// Set back to default so as not to break other tests.
Parse.User.allowCustomUserClass(false);
@@ -1734,7 +1729,7 @@ describe('Parse.Object testing', () => {
return t3.fetch();
}).then(function(t3) {
equal(t3.get("test"), "test",
"Fetch should have grabbed server 'test' property.");
"Fetch should have grabbed server 'test' property.");
done();
}, function(error) {
ok(false, error);
@@ -1763,7 +1758,7 @@ describe('Parse.Object testing', () => {
var obj2 = new TestObject();
obj2.increment('astring');
return obj2.save();
}).then((obj2) => {
}).then(() => {
fail('Should not have saved.');
done();
}, (error) => {
@@ -1847,10 +1842,10 @@ describe('Parse.Object testing', () => {
"_nested": "key"
}
});
object.save().then( res => {
object.save().then(res => {
ok(res);
return res.fetch();
}).then( res => {
}).then(res => {
const foo = res.get("foo");
expect(foo["_bar"]).toEqual("_");
expect(foo["baz_bar"]).toEqual(1);
@@ -1858,7 +1853,7 @@ describe('Parse.Object testing', () => {
expect(foo["_0"]).toEqual("underscore_zero");
expect(foo["_more"]["_nested"]).toEqual("key");
done();
}).fail( err => {
}).fail(err => {
jfail(err);
fail("should not fail");
done();
@@ -1866,15 +1861,15 @@ describe('Parse.Object testing', () => {
});
it('should have undefined includes when object is missing', (done) => {
let obj1 = new Parse.Object("AnObject");
let obj2 = new Parse.Object("AnObject");
const obj1 = new Parse.Object("AnObject");
const obj2 = new Parse.Object("AnObject");
Parse.Object.saveAll([obj1, obj2]).then(() => {
Parse.Object.saveAll([obj1, obj2]).then(() => {
obj1.set("obj", obj2);
// Save the pointer, delete the pointee
return obj1.save().then(() => { return obj2.destroy() });
}).then(() => {
let query = new Parse.Query("AnObject");
const query = new Parse.Query("AnObject");
query.include("obj");
return query.find();
}).then((res) => {
@@ -1882,9 +1877,9 @@ describe('Parse.Object testing', () => {
if (res[0]) {
expect(res[0].get("obj")).toBe(undefined);
}
let query = new Parse.Query("AnObject");
const query = new Parse.Query("AnObject");
return query.find();
}).then((res) => {
}).then((res) => {
expect(res.length).toBe(1);
if (res[0]) {
expect(res[0].get("obj")).not.toBe(undefined);
@@ -1901,16 +1896,16 @@ describe('Parse.Object testing', () => {
});
it('should have undefined includes when object is missing on deeper path', (done) => {
let obj1 = new Parse.Object("AnObject");
let obj2 = new Parse.Object("AnObject");
let obj3 = new Parse.Object("AnObject");
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const obj1 = new Parse.Object("AnObject");
const obj2 = new Parse.Object("AnObject");
const obj3 = new Parse.Object("AnObject");
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
obj1.set("obj", obj2);
obj2.set("obj", obj3);
// Save the pointer, delete the pointee
return Parse.Object.saveAll([obj1, obj2]).then(() => { return obj3.destroy() });
}).then(() => {
let query = new Parse.Query("AnObject");
const query = new Parse.Query("AnObject");
query.include("obj.obj");
return query.get(obj1.id);
}).then((res) => {
@@ -1924,9 +1919,9 @@ describe('Parse.Object testing', () => {
});
it('should handle includes on null arrays #2752', (done) => {
let obj1 = new Parse.Object("AnObject");
let obj2 = new Parse.Object("AnotherObject");
let obj3 = new Parse.Object("NestedObject");
const obj1 = new Parse.Object("AnObject");
const obj2 = new Parse.Object("AnotherObject");
const obj3 = new Parse.Object("NestedObject");
obj3.set({
"foo": "bar"
})
@@ -1934,17 +1929,17 @@ describe('Parse.Object testing', () => {
"key": obj3
})
Parse.Object.saveAll([obj1, obj2]).then(() => {
Parse.Object.saveAll([obj1, obj2]).then(() => {
obj1.set("objects", [null, null, obj2]);
return obj1.save();
}).then(() => {
let query = new Parse.Query("AnObject");
const query = new Parse.Query("AnObject");
query.include("objects.key");
return query.find();
}).then((res) => {
let obj = res[0];
const obj = res[0];
expect(obj.get("objects")).not.toBe(undefined);
let array = obj.get("objects");
const array = obj.get("objects");
expect(Array.isArray(array)).toBe(true);
expect(array[0]).toBe(null);
expect(array[1]).toBe(null);
@@ -1957,25 +1952,25 @@ describe('Parse.Object testing', () => {
});
it('should handle select and include #2786', (done) => {
let score = new Parse.Object("GameScore");
let player = new Parse.Object("Player");
const score = new Parse.Object("GameScore");
const player = new Parse.Object("Player");
score.set({
"score": 1234
});
score.save().then(() => {
score.save().then(() => {
player.set("gameScore", score);
player.set("other", "value");
return player.save();
}).then(() => {
let query = new Parse.Query("Player");
const query = new Parse.Query("Player");
query.include("gameScore");
query.select("gameScore");
return query.find();
}).then((res) => {
let obj = res[0];
let gameScore = obj.get("gameScore");
let other = obj.get("other");
const obj = res[0];
const gameScore = obj.get("gameScore");
const other = obj.get("other");
expect(other).toBeUndefined();
expect(gameScore).not.toBeUndefined();
expect(gameScore.get("score")).toBe(1234);
+262
View File
@@ -0,0 +1,262 @@
const TestObject = Parse.Object.extend('TestObject');
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const rp = require('request-promise');
const defaultHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest'
}
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]];
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((result) => {
const polygon = result.get('polygon');
equal(polygon.__type, 'Polygon');
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]];
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((result) => {
const polygon = result.get('polygon');
equal(polygon.__type, 'Polygon');
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]];
const openPolygon = {__type: 'Polygon', coordinates: openPoints};
const closedPolygon = {__type: 'Polygon', coordinates: 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.__type, 'Polygon');
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.__type, 'Polygon');
equal(polygon.coordinates, closedPoints);
done();
}, done.fail);
});
it('polygon update', (done) => {
const oldCoords = [[0,0],[0,1],[1,1],[1,0]];
const oldPolygon = {__type: 'Polygon', coordinates: oldCoords};
const newCoords = [[2,2],[2,3],[3,3],[3,2]];
const newPolygon = {__type: 'Polygon', coordinates: 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.__type, 'Polygon');
equal(polygon.coordinates, newCoords);
done();
}, done.fail);
});
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);
});
it('polygon three points minimum', (done) => {
const coords = [[0,0]];
const obj = new TestObject();
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]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
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]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon.__type, 'Polygon');
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 = {__type: 'Polygon', coordinates: points1};
const polygon2 = {__type: 'Polygon', coordinates: points2};
const polygon3 = {__type: 'Polygon', coordinates: 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);
});
it('polygonContain invalid input', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = {__type: 'Polygon', coordinates: 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 invalid geoPoint', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = {__type: 'Polygon', coordinates: 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);
});
});
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]];
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);
});
it('polygon loop is not valid', (done) => {
const coords = [[0,0],[0,1],[1,0],[1,1]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(done.fail, done);
});
});
+6 -6
View File
@@ -19,7 +19,7 @@ describe('ParsePubSub', function() {
});
it('can create redis publisher', function() {
var publisher = ParsePubSub.createPublisher({
ParsePubSub.createPublisher({
redisURL: 'redisURL'
});
@@ -30,7 +30,7 @@ describe('ParsePubSub', function() {
});
it('can create event emitter publisher', function() {
var publisher = ParsePubSub.createPublisher({});
ParsePubSub.createPublisher({});
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
@@ -39,7 +39,7 @@ describe('ParsePubSub', function() {
});
it('can create redis subscriber', function() {
var subscriber = ParsePubSub.createSubscriber({
ParsePubSub.createSubscriber({
redisURL: 'redisURL'
});
@@ -50,7 +50,7 @@ describe('ParsePubSub', function() {
});
it('can create event emitter subscriber', function() {
var subscriptionInfos = ParsePubSub.createSubscriber({});
ParsePubSub.createSubscriber({});
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
@@ -59,7 +59,7 @@ describe('ParsePubSub', function() {
});
it('can create publisher/sub with custom adapter', function() {
let adapter = {
const adapter = {
createPublisher: jasmine.createSpy('createPublisher'),
createSubscriber: jasmine.createSpy('createSubscriber')
}
@@ -82,7 +82,7 @@ describe('ParsePubSub', function() {
});
it('can create publisher/sub with custom function adapter', function() {
let adapter = {
const adapter = {
createPublisher: jasmine.createSpy('createPublisher'),
createSubscriber: jasmine.createSpy('createSubscriber')
}
+456
View File
@@ -0,0 +1,456 @@
'use strict';
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
const Parse = require('parse/node');
const rp = require('request-promise');
let databaseAdapter;
const fullTextHelper = () => {
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
if (!databaseAdapter) {
databaseAdapter = new PostgresStorageAdapter({ uri: postgresURI });
}
} else {
databaseAdapter = new MongoStorageAdapter({ uri: mongoURI });
}
const subjects = [
'coffee',
'Coffee Shopping',
'Baking a cake',
'baking',
'Café Con Leche',
'Сырники',
'coffee and cream',
'Cafe con Leche',
];
const requests = [];
for (const i in subjects) {
const request = {
method: "POST",
body: {
subject: subjects[i]
},
path: "/1/classes/TestObject"
};
requests.push(request);
}
return reconfigureServer({
appId: 'test',
restAPIKey: 'test',
publicServerURL: 'http://localhost:8378/1',
databaseAdapter
}).then(() => {
return rp.post({
url: 'http://localhost:8378/1/batch',
body: {
requests
},
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
});
}
describe('Parse.Query Full Text Search testing', () => {
it('fullTextSearch: $search', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'coffee'
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
expect(resp.results.length).toBe(3);
done();
}, done.fail);
});
it('fullTextSearch: $search, sort', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'coffee'
}
}
}
};
const order = '$score';
const keys = '$score';
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, order, keys, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
expect(resp.results.length).toBe(3);
expect(resp.results[0].score);
expect(resp.results[1].score);
expect(resp.results[2].score);
done();
}, done.fail);
});
it('fullTextSearch: $language', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'leche',
$language: 'spanish'
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
expect(resp.results.length).toBe(2);
done();
}, done.fail);
});
it('fullTextSearch: $diacriticSensitive', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'CAFÉ',
$diacriticSensitive: true
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
expect(resp.results.length).toBe(1);
done();
}, done.fail);
});
it('fullTextSearch: $search, invalid input', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: true
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
it('fullTextSearch: $language, invalid input', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'leche',
$language: true
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
it('fullTextSearch: $caseSensitive, invalid input', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'Coffee',
$caseSensitive: 'string'
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
it('fullTextSearch: $diacriticSensitive, invalid input', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'CAFÉ',
$diacriticSensitive: 'string'
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`no request should succeed: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
});
describe_only_db('mongo')('Parse.Query Full Text Search testing', () => {
it('fullTextSearch: $search, only one text index', (done) => {
return reconfigureServer({
appId: 'test',
restAPIKey: 'test',
publicServerURL: 'http://localhost:8378/1',
databaseAdapter: new MongoStorageAdapter({ uri: mongoURI })
}).then(() => {
return rp.post({
url: 'http://localhost:8378/1/batch',
body: {
requests: [
{
method: "POST",
body: {
subject: "coffee is java"
},
path: "/1/classes/TestObject"
},
{
method: "POST",
body: {
subject: "java is coffee"
},
path: "/1/classes/TestObject"
}
]
},
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then(() => {
return databaseAdapter.createIndex('TestObject', {random: 'text'});
}).then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'coffee'
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`Should not be more than one text index: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INTERNAL_SERVER_ERROR);
done();
});
});
it('fullTextSearch: $diacriticSensitive - false', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'CAFÉ',
$diacriticSensitive: false
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
expect(resp.results.length).toBe(2);
done();
}, done.fail);
});
it('fullTextSearch: $caseSensitive', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'Coffee',
$caseSensitive: true
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
expect(resp.results.length).toBe(1);
done();
}, done.fail);
});
});
describe_only_db('postgres')('Parse.Query Full Text Search testing', () => {
it('fullTextSearch: $diacriticSensitive - false', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'CAFÉ',
$diacriticSensitive: false
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`$diacriticSensitive - false should not supported: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
it('fullTextSearch: $caseSensitive', (done) => {
fullTextHelper().then(() => {
const where = {
subject: {
$text: {
$search: {
$term: 'Coffee',
$caseSensitive: true
}
}
}
};
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test'
}
});
}).then((resp) => {
fail(`$caseSensitive should not supported: ${JSON.stringify(resp)}`);
done();
}).catch((err) => {
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});
});
+665 -340
View File
File diff suppressed because it is too large Load Diff
+210 -177
View File
@@ -22,11 +22,11 @@ describe('Parse.Relation testing', () => {
return relation.query().find();
}).then((list) => {
equal(list.length, 1,
"Should have gotten one element back");
"Should have gotten one element back");
equal(list[0].id, child.id,
"Should have gotten the right value");
"Should have gotten the right value");
ok(!parent.dirty("child"),
"The relation should not be dirty");
"The relation should not be dirty");
relation.remove(child);
return parent.save();
@@ -34,9 +34,9 @@ describe('Parse.Relation testing', () => {
return relation.query().find();
}).then((list) => {
equal(list.length, 0,
"Delete should have worked");
"Delete should have worked");
ok(!parent.dirty("child"),
"The relation should not be dirty");
"The relation should not be dirty");
done();
});
});
@@ -46,10 +46,10 @@ describe('Parse.Relation testing', () => {
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x:i}));
};
}
Parse.Object.saveAll(childObjects, expectSuccess({
success: function(list) {
success: function() {
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
@@ -63,9 +63,9 @@ describe('Parse.Relation testing', () => {
relation.query().find(expectSuccess({
success: function(list) {
equal(list.length, 1,
"Should have gotten one element back");
"Should have gotten one element back");
equal(list[0].id, childObjects[0].id,
"Should have gotten the right value");
"Should have gotten the right value");
done();
}
}));
@@ -84,7 +84,7 @@ describe('Parse.Relation testing', () => {
}
Parse.Object.saveAll(childObjects, {
success: function(list) {
success: function() {
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
@@ -99,14 +99,14 @@ describe('Parse.Relation testing', () => {
relationAgain.query().find({
success: function(list) {
equal(list.length, 1,
"Should have gotten one element back");
"Should have gotten one element back");
equal(list[0].id, childObjects[0].id,
"Should have gotten the right value");
"Should have gotten the right value");
ok(!parent.dirty("child"),
"The relation should not be dirty");
"The relation should not be dirty");
done();
},
error: function(list) {
error: function() {
ok(false, "This shouldn't have failed");
done();
}
@@ -131,7 +131,7 @@ describe('Parse.Relation testing', () => {
var parent;
var relation;
Parse.Object.saveAll(childObjects).then(function(list) {
Parse.Object.saveAll(childObjects).then(function() {
var ParentObject = Parse.Object.extend('ParentObject');
parent = new ParentObject();
parent.set('x', 4);
@@ -188,11 +188,11 @@ describe('Parse.Relation testing', () => {
query.find({
success: function(list) {
equal(list.length, 1,
"There should only be one element");
"There should only be one element");
ok(list[0] instanceof ChildObject,
"Should be of type ChildObject");
"Should be of type ChildObject");
equal(list[0].id, childObjects[2].id,
"We should have gotten back the right result");
"We should have gotten back the right result");
done();
}
});
@@ -238,7 +238,7 @@ describe('Parse.Relation testing', () => {
success: function(list) {
equal(list.length, 1, "There should be only one result");
equal(list[0].id, parent2.id,
"Should have gotten back the right result");
"Should have gotten back the right result");
done();
}
});
@@ -249,35 +249,35 @@ describe('Parse.Relation testing', () => {
});
it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
let ChildObject = Parse.Object.extend("ChildObject");
let childObjects = [];
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects).then(() => {
let ParentObject = Parse.Object.extend("ParentObject");
let parent = new ParentObject();
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
parent.set("x", 4);
let parent1Children = parent.relation("child");
const parent1Children = parent.relation("child");
parent1Children.add(childObjects[0]);
parent1Children.add(childObjects[1]);
parent1Children.add(childObjects[2]);
let parent2 = new ParentObject();
const parent2 = new ParentObject();
parent2.set("x", 3);
let parent2Children = parent2.relation("child");
const parent2Children = parent2.relation("child");
parent2Children.add(childObjects[4]);
parent2Children.add(childObjects[5]);
parent2Children.add(childObjects[6]);
let parent2OtherChildren = parent2.relation("otherChild");
const parent2OtherChildren = parent2.relation("otherChild");
parent2OtherChildren.add(childObjects[0]);
parent2OtherChildren.add(childObjects[1]);
parent2OtherChildren.add(childObjects[2]);
return Parse.Object.saveAll([parent, parent2]);
}).then(() => {
let objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
const objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
return objectsWithChild0InBothChildren.find();
@@ -285,7 +285,7 @@ describe('Parse.Relation testing', () => {
//No parent has child 0 in both it's "child" and "otherChild" field;
expect(objectsWithChild0InBothChildren.length).toEqual(0);
}).then(() => {
let objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
const objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
return objectsWithChild4andOtherChild1.find();
@@ -304,34 +304,34 @@ describe('Parse.Relation testing', () => {
}
Parse.Object.saveAll(childObjects).then(() => {
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.set("toChild", childObjects[2]);
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.set("toChild", childObjects[2]);
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
return Parse.Object.saveAll(parents).then(() => {
var query = new Parse.Query(ParentObject);
query.equalTo("objectId", parent.id);
query.equalTo("toChilds", childObjects[2]);
return Parse.Object.saveAll(parents).then(() => {
var query = new Parse.Query(ParentObject);
query.equalTo("objectId", parent.id);
query.equalTo("toChilds", childObjects[2]);
return query.find().then((list) => {
equal(list.length, 1, "There should be 1 result");
done();
});
return query.find().then((list) => {
equal(list.length, 1, "There should be 1 result");
done();
});
}).catch(err => {
});
}).catch(err => {
jfail(err);
done();
});
@@ -345,35 +345,35 @@ describe('Parse.Relation testing', () => {
}
Parse.Object.saveAll(childObjects).then(() => {
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.relation("toChilds").add(childObjects[2]);
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.relation("toChilds").add(childObjects[2]);
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
return Parse.Object.saveAll(parents).then(() => {
var query = new Parse.Query(ParentObject);
query.equalTo("objectId", parent2.id);
// childObjects[2] is in 2 relations
// before the fix, that woul yield 2 results
query.equalTo("toChilds", childObjects[2]);
return Parse.Object.saveAll(parents).then(() => {
var query = new Parse.Query(ParentObject);
query.equalTo("objectId", parent2.id);
// childObjects[2] is in 2 relations
// before the fix, that woul yield 2 results
query.equalTo("toChilds", childObjects[2]);
return query.find().then((list) => {
equal(list.length, 1, "There should be 1 result");
done();
});
return query.find().then((list) => {
equal(list.length, 1, "There should be 1 result");
done();
});
});
});
});
@@ -385,39 +385,39 @@ describe('Parse.Relation testing', () => {
}
Parse.Object.saveAll(childObjects).then(() => {
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.set("toChild", childObjects[2]);
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.set("toChild", childObjects[2]);
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
return Parse.Object.saveAll(parents).then(() => {
var query1 = new Parse.Query(ParentObject);
query1.containedIn("toChilds", [childObjects[2]]);
var query2 = new Parse.Query(ParentObject);
query2.equalTo("toChild", childObjects[2]);
var query = Parse.Query.or(query1, query2);
return query.find().then((list) => {
var objectIds = list.map(function(item){
return item.id;
});
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
equal(list.length, 2, "There should be 2 results");
done();
return Parse.Object.saveAll(parents).then(() => {
var query1 = new Parse.Query(ParentObject);
query1.containedIn("toChilds", [childObjects[2]]);
var query2 = new Parse.Query(ParentObject);
query2.equalTo("toChild", childObjects[2]);
var query = Parse.Query.or(query1, query2);
return query.find().then((list) => {
var objectIds = list.map(function(item){
return item.id;
});
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
equal(list.length, 2, "There should be 2 results");
done();
});
});
});
});
@@ -510,24 +510,24 @@ describe('Parse.Relation testing', () => {
});
it('should properly get related objects with unfetched queries', (done) => {
let objects = [];
let owners = [];
let allObjects = [];
const objects = [];
const owners = [];
const allObjects = [];
// Build 10 Objects and 10 owners
while (objects.length != 10) {
let object = new Parse.Object('AnObject');
const object = new Parse.Object('AnObject');
object.set({
index: objects.length,
even: objects.length % 2 == 0
});
objects.push(object);
let owner = new Parse.Object('AnOwner');
const owner = new Parse.Object('AnOwner');
owners.push(owner);
allObjects.push(object);
allObjects.push(owner);
}
let anotherOwner = new Parse.Object('AnotherOwner');
const anotherOwner = new Parse.Object('AnotherOwner');
return Parse.Object.saveAll(allObjects.concat([anotherOwner])).then(() => {
// put all the AnObject into the anotherOwner relationKey
@@ -539,42 +539,45 @@ describe('Parse.Relation testing', () => {
return Parse.Object.saveAll(owners.concat([anotherOwner]));
}).then(() => {
// Query on the relation of another owner
let object = new Parse.Object('AnotherOwner');
const object = new Parse.Object('AnotherOwner');
object.id = anotherOwner.id;
let relationQuery = object.relation('relationKey').query();
const relationQuery = object.relation('relationKey').query();
// Just get the even ones
relationQuery.equalTo('even', true);
// Make the query on anOwner
let query = new Parse.Query('AnOwner');
const query = new Parse.Query('AnOwner');
// where key match the relation query.
query.matchesQuery('key', relationQuery);
query.include('key');
return query.find();
}).then((results) => {
expect(results.length).toBe(5);
results.forEach((result) => {
results.forEach((result) => {
expect(result.get('key').get('even')).toBe(true);
});
return Promise.resolve();
}).then(() => {
}).then(() => {
// Query on the relation of another owner
let object = new Parse.Object('AnotherOwner');
const object = new Parse.Object('AnotherOwner');
object.id = anotherOwner.id;
let relationQuery = object.relation('relationKey').query();
const relationQuery = object.relation('relationKey').query();
// Just get the even ones
relationQuery.equalTo('even', true);
// Make the query on anOwner
let query = new Parse.Query('AnOwner');
const query = new Parse.Query('AnOwner');
// where key match the relation query.
query.doesNotMatchQuery('key', relationQuery);
query.include('key');
return query.find();
}).then((results) => {
expect(results.length).toBe(5);
results.forEach((result) => {
results.forEach((result) => {
expect(result.get('key').get('even')).toBe(false);
});
done();
}, (e) => {
fail(JSON.stringify(e));
done();
})
});
@@ -586,21 +589,19 @@ describe('Parse.Relation testing', () => {
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
];
let persons = [
const persons = [
new PersonObject({ name: "Bob", hometown: "Djibouti" }),
new PersonObject({ name: "Tom", hometown: "Ouagadougou" }),
new PersonObject({ name: "Billy", hometown: "Detroit" }),
];
let owner = new OwnerObject({name: 'Joe'});
let ownerId;
let allObjects = [owner].concat(restaurants).concat(persons);
const owner = new OwnerObject({name: 'Joe'});
const allObjects = [owner].concat(restaurants).concat(persons);
expect(allObjects.length).toEqual(6);
Parse.Object.saveAll([owner].concat(restaurants).concat(persons)).then(function() {
ownerId = owner.id;
owner.relation('restaurants').add(restaurants);
return owner.save()
}).then(() => {
let unfetchedOwner = new OwnerObject();
const unfetchedOwner = new OwnerObject();
unfetchedOwner.id = owner.id;
var query = unfetchedOwner.relation('restaurants').query();
query.greaterThan("ratings", 4);
@@ -615,6 +616,9 @@ describe('Parse.Relation testing', () => {
done();
}
}));
}, (e) => {
fail(JSON.stringify(e));
done();
});
});
@@ -626,21 +630,19 @@ describe('Parse.Relation testing', () => {
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
];
let persons = [
const persons = [
new PersonObject({ name: "Bob", hometown: "Djibouti" }),
new PersonObject({ name: "Tom", hometown: "Ouagadougou" }),
new PersonObject({ name: "Billy", hometown: "Detroit" }),
];
let owner = new OwnerObject({name: 'Joe'});
let ownerId;
let allObjects = [owner].concat(restaurants).concat(persons);
const owner = new OwnerObject({name: 'Joe'});
const allObjects = [owner].concat(restaurants).concat(persons);
expect(allObjects.length).toEqual(6);
Parse.Object.saveAll([owner].concat(restaurants).concat(persons)).then(function() {
ownerId = owner.id;
owner.relation('restaurants').add(restaurants);
return owner.save()
}).then(() => {
let unfetchedOwner = new OwnerObject();
const unfetchedOwner = new OwnerObject();
unfetchedOwner.id = owner.id;
var query = unfetchedOwner.relation('restaurants').query();
query.greaterThan("ratings", 4);
@@ -657,25 +659,28 @@ describe('Parse.Relation testing', () => {
done();
}
}));
}, (e) => {
fail(JSON.stringify(e));
done();
});
});
it('relations are not bidirectional (regression test for #871)', done => {
let PersonObject = Parse.Object.extend("Person");
let p1 = new PersonObject();
let p2 = new PersonObject();
const PersonObject = Parse.Object.extend("Person");
const p1 = new PersonObject();
const p2 = new PersonObject();
Parse.Object.saveAll([p1, p2]).then(results => {
let p1 = results[0];
let p2 = results[1];
let relation = p1.relation('relation');
const p1 = results[0];
const p2 = results[1];
const relation = p1.relation('relation');
relation.add(p2);
p1.save().then(() => {
let query = new Parse.Query(PersonObject);
const query = new Parse.Query(PersonObject);
query.equalTo('relation', p1);
query.find().then(results => {
expect(results.length).toEqual(0);
let query = new Parse.Query(PersonObject);
const query = new Parse.Query(PersonObject);
query.equalTo('relation', p2);
query.find().then(results => {
expect(results.length).toEqual(1);
@@ -689,56 +694,84 @@ describe('Parse.Relation testing', () => {
it('can query roles in Cloud Code (regession test #1489)', done => {
Parse.Cloud.define('isAdmin', (request, response) => {
let query = new Parse.Query(Parse.Role);
const query = new Parse.Query(Parse.Role);
query.equalTo('name', 'admin');
query.first({ useMasterKey: true })
.then(role => {
let relation = new Parse.Relation(role, 'users');
let admins = relation.query();
admins.equalTo('username', request.user.get('username'));
admins.first({ useMasterKey: true })
.then(user => {
if (user) {
response.success(user);
done();
} else {
fail('Should have found admin user, found nothing instead');
done();
}
.then(role => {
const relation = new Parse.Relation(role, 'users');
const admins = relation.query();
admins.equalTo('username', request.user.get('username'));
admins.first({ useMasterKey: true })
.then(user => {
if (user) {
response.success(user);
done();
} else {
fail('Should have found admin user, found nothing instead');
done();
}
}, () => {
fail('User not admin');
done();
})
}, error => {
fail('User not admin');
fail('Should have found admin user, errored instead');
fail(error);
done();
})
}, error => {
fail('Should have found admin user, errored instead');
fail(error);
done();
});
});
});
let adminUser = new Parse.User();
const adminUser = new Parse.User();
adminUser.set('username', 'name');
adminUser.set('password', 'pass');
adminUser.signUp()
.then(adminUser => {
let adminACL = new Parse.ACL();
adminACL.setPublicReadAccess(true);
.then(adminUser => {
const adminACL = new Parse.ACL();
adminACL.setPublicReadAccess(true);
// Create admin role
let adminRole = new Parse.Role('admin', adminACL);
adminRole.getUsers().add(adminUser);
adminRole.save()
.then(() => {
Parse.Cloud.run('isAdmin');
// Create admin role
const adminRole = new Parse.Role('admin', adminACL);
adminRole.getUsers().add(adminUser);
adminRole.save()
.then(() => {
Parse.Cloud.run('isAdmin');
}, error => {
fail('failed to save role');
fail(error);
done()
});
}, error => {
fail('failed to save role');
fail('failed to sign up');
fail(error);
done()
done();
});
});
it('can be saved without error', done => {
const obj1 = new Parse.Object('PPAP');
obj1.save()
.then(() => {
const newRelation = obj1.relation('aRelation');
newRelation.add(obj1);
obj1.save().then(() => {
const relation = obj1.get('aRelation');
obj1.set('aRelation', relation);
obj1.save().then(() => {
done();
}, error => {
fail('failed to save ParseRelation object');
fail(error);
done();
});
}, error => {
fail('failed to create relation field');
fail(error);
done();
});
}, error => {
fail('failed to save obj');
fail(error);
done();
});
}, error => {
fail('failed to sign up');
fail(error);
done();
});
});
});
+248 -68
View File
@@ -13,7 +13,7 @@ describe('Parse Role testing', () => {
createTestUser().then((x) => {
user = x;
let acl = new Parse.ACL();
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(false);
role = new Parse.Object('_Role');
@@ -22,7 +22,7 @@ describe('Parse Role testing', () => {
var users = role.relation('users');
users.add(user);
return role.save({}, { useMasterKey: true });
}).then((x) => {
}).then(() => {
var query = new Parse.Query('_Role');
return query.find({ useMasterKey: true });
}).then((x) => {
@@ -42,7 +42,7 @@ describe('Parse Role testing', () => {
acl.setRoleWriteAccess('Foos', true);
obj.setACL(acl);
return obj.save();
}).then((x) => {
}).then(() => {
var query = new Parse.Query('TestObject');
return query.find({ sessionToken: user.getSessionToken() });
}).then((x) => {
@@ -55,7 +55,7 @@ describe('Parse Role testing', () => {
x.set('foo', 'baz');
// This should fail:
return x.save({},{sessionToken: ""});
}).then((x) => {
}).then(() => {
fail('Should not have been able to save.');
}, (e) => {
expect(e.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
@@ -98,10 +98,10 @@ describe('Parse Role testing', () => {
var user,
auth,
getAllRolesSpy;
createTestUser().then( (newUser) => {
createTestUser().then((newUser) => {
user = newUser;
return createAllRoles(user);
}).then ( (roles) => {
}).then ((roles) => {
var rootRoleObj = roleObjs[rootRole];
roles.forEach(function(role, i) {
// Add all roles to the RootRole
@@ -115,16 +115,16 @@ describe('Parse Role testing', () => {
});
return Parse.Object.saveAll(roles, { useMasterKey: true });
}).then( () => {
}).then(() => {
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
getAllRolesSpy = spyOn(auth, "_getAllRolesNamesForRoleIds").and.callThrough();
return auth._loadRoles();
}).then ( (roles) => {
}).then ((roles) => {
expect(roles.length).toEqual(4);
allRoles.forEach(function(name) {
expect(roles.indexOf("role:"+name)).not.toBe(-1);
expect(roles.indexOf("role:" + name)).not.toBe(-1);
});
// 1 Query for the initial setup
@@ -135,7 +135,7 @@ describe('Parse Role testing', () => {
// 1 call for the 2nd layer
expect(getAllRolesSpy.calls.count()).toEqual(2);
done()
}).catch( (err) => {
}).catch(() => {
fail("should succeed");
done();
});
@@ -145,70 +145,88 @@ describe('Parse Role testing', () => {
it("should recursively load roles", (done) => {
var rolesNames = ["FooRole", "BarRole", "BazRole"];
var roleIds = {};
createTestUser().then( (user) => {
// Put the user on the 1st role
return createRole(rolesNames[0], null, user).then( (aRole) => {
roleIds[aRole.get("name")] = aRole.id;
// set the 1st role as a sibling of the second
// user will should have 2 role now
return createRole(rolesNames[1], aRole, null);
}).then( (anotherRole) => {
roleIds[anotherRole.get("name")] = anotherRole.id;
// set this role as a sibling of the last
// the user should now have 3 roles
return createRole(rolesNames[2], anotherRole, null);
}).then( (lastRole) => {
roleIds[lastRole.get("name")] = lastRole.id;
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
return auth._loadRoles();
})
}).then( (roles) => {
expect(roles.length).toEqual(3);
rolesNames.forEach( (name) => {
expect(roles.indexOf('role:'+name)).not.toBe(-1);
});
done();
}, function(err){
fail("should succeed")
done();
});
createTestUser().then((user) => {
// Put the user on the 1st role
return createRole(rolesNames[0], null, user).then((aRole) => {
roleIds[aRole.get("name")] = aRole.id;
// set the 1st role as a sibling of the second
// user will should have 2 role now
return createRole(rolesNames[1], aRole, null);
}).then((anotherRole) => {
roleIds[anotherRole.get("name")] = anotherRole.id;
// set this role as a sibling of the last
// the user should now have 3 roles
return createRole(rolesNames[2], anotherRole, null);
}).then((lastRole) => {
roleIds[lastRole.get("name")] = lastRole.id;
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
return auth._loadRoles();
})
}).then((roles) => {
expect(roles.length).toEqual(3);
rolesNames.forEach((name) => {
expect(roles.indexOf('role:' + name)).not.toBe(-1);
});
done();
}, function(){
fail("should succeed")
done();
});
});
it("_Role object should not save without name.", (done) => {
var role = new Parse.Role();
role.save(null,{useMasterKey:true})
.then((r) => {
fail("_Role object should not save without name.");
}, (error) => {
expect(error.code).toEqual(111);
role.set('name','testRole');
role.save(null,{useMasterKey:true})
.then((r2)=>{
fail("_Role object should not save without ACL.");
}, (error2) =>{
expect(error2.code).toEqual(111);
done();
.then(() => {
fail("_Role object should not save without name.");
}, (error) => {
expect(error.code).toEqual(111);
role.set('name','testRole');
role.save(null,{useMasterKey:true})
.then(()=>{
fail("_Role object should not save without ACL.");
}, (error2) =>{
expect(error2.code).toEqual(111);
done();
});
});
});
it("Different _Role objects cannot have the same name.", (done) => {
const roleName = "MyRole";
let aUser;
createTestUser().then((user) => {
aUser = user;
return createRole(roleName, null, aUser);
}).then((firstRole) => {
expect(firstRole.getName()).toEqual(roleName);
return createRole(roleName, null, aUser);
}).then(() => {
fail("_Role cannot have the same name as another role");
done();
}, (error) => {
expect(error.code).toEqual(137);
done();
});
});
it("Should properly resolve roles", (done) => {
let admin = new Parse.Role("Admin", new Parse.ACL());
let moderator = new Parse.Role("Moderator", new Parse.ACL());
let superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
let contentManager = new Parse.Role('ContentManager', new Parse.ACL());
let superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
it("Should properly resolve roles", (done) => {
const admin = new Parse.Role("Admin", new Parse.ACL());
const moderator = new Parse.Role("Moderator", new Parse.ACL());
const superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
const contentManager = new Parse.Role('ContentManager', new Parse.ACL());
const superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
contentManager.getRoles().add([moderator, superContentManager]);
moderator.getRoles().add([admin, superModerator]);
superContentManager.getRoles().add(superModerator);
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
}).then(() => {
}).then(() => {
var auth = new Auth({ config: new Config("test"), isMaster: true });
// For each role, fetch their sibling, what they inherit
// return with result and roleId for later comparison
let promises = [admin, moderator, contentManager, superModerator].map((role) => {
return auth._getAllRolesNamesForRoleIds([role.id]).then((result) => {
const promises = [admin, moderator, contentManager, superModerator].map((role) => {
return auth._getAllRolesNamesForRoleIds([role.id]).then((result) => {
return Parse.Promise.as({
id: role.id,
name: role.get('name'),
@@ -220,8 +238,8 @@ describe('Parse Role testing', () => {
return Parse.Promise.when(promises);
}).then((results) => {
results.forEach((result) => {
let id = result.id;
let roleNames = result.roleNames;
const id = result.id;
const roleNames = result.roleNames;
if (id == admin.id) {
expect(roleNames.length).toBe(2);
expect(roleNames.indexOf("Moderator")).not.toBe(-1);
@@ -239,7 +257,7 @@ describe('Parse Role testing', () => {
}
});
done();
}).fail((err) => {
}).fail(() => {
done();
})
@@ -250,16 +268,16 @@ describe('Parse Role testing', () => {
roleACL.setPublicReadAccess(true);
var role = new Parse.Role('subscribers', roleACL);
role.save({}, {useMasterKey : true})
.then((x)=>{
.then(()=>{
var query = role.relation('users').query();
query.find({useMasterKey : true})
.then((users)=>{
.then(()=>{
done();
}, (e)=>{
}, ()=>{
fail('should not have errors');
done();
});
}, (e) => {
}, () => {
fail('should not have errored');
});
});
@@ -278,10 +296,10 @@ describe('Parse Role testing', () => {
user = x;
user2 = new Parse.User();
return user2.save({ username: 'user2', password: 'omgbbq' });
}).then((x) => {
}).then(() => {
user3 = new Parse.User();
return user3.save({ username: 'user3', password: 'omgbbq' });
}).then((x) => {
}).then(() => {
role = new Parse.Role('Admin', prACL);
role.getUsers().add(user);
return role.save({}, { useMasterKey: true });
@@ -327,7 +345,7 @@ describe('Parse Role testing', () => {
obj2 = new Parse.Object('TestObjectRoles');
obj2.set('ACL', adminACL);
return obj2.save(null, { useMasterKey: true });
}, (e) => {
}, () => {
fail('Admin user should have been able to save.');
done();
}).then(() => {
@@ -347,4 +365,166 @@ describe('Parse Role testing', () => {
})
});
it('should add multiple users to a role and remove users', (done) => {
var user, user2, user3;
var role;
var obj;
var prACL = new Parse.ACL();
prACL.setPublicReadAccess(true);
prACL.setPublicWriteAccess(true);
createTestUser().then((x) => {
user = x;
user2 = new Parse.User();
return user2.save({ username: 'user2', password: 'omgbbq' });
}).then(() => {
user3 = new Parse.User();
return user3.save({ username: 'user3', password: 'omgbbq' });
}).then(() => {
role = new Parse.Role('sharedRole', prACL);
var users = role.relation('users');
users.add(user);
users.add(user2);
users.add(user3);
return role.save({}, { useMasterKey: true });
}).then(() => {
// query for saved role and get 3 users
var query = new Parse.Query('_Role');
query.equalTo('name', 'sharedRole');
return query.find({ useMasterKey: true });
}).then((role) => {
expect(role.length).toEqual(1);
var users = role[0].relation('users').query();
return users.find({ useMasterKey: true });
}).then((users) => {
expect(users.length).toEqual(3);
obj = new Parse.Object('TestObjectRoles');
obj.set('ACL', prACL);
return obj.save(null, { useMasterKey: true });
}).then(() => {
// Above, the Admin role was added to the Customer role.
// An object secured by the Customer ACL should be able to be edited by the Admin user.
obj.set('changedByUsers', true);
return obj.save(null, { sessionToken: user.getSessionToken() });
}).then(() => {
// query for saved role and get 3 users
var query = new Parse.Query('_Role');
query.equalTo('name', 'sharedRole');
return query.find({ useMasterKey: true });
}).then((role) => {
expect(role.length).toEqual(1);
var users = role[0].relation('users');
users.remove(user);
users.remove(user3);
return role[0].save({}, { useMasterKey: true });
}).then((role) =>{
var users = role.relation('users').query();
return users.find({ useMasterKey: true });
}).then((users) => {
expect(users.length).toEqual(1);
expect(users[0].get('username')).toEqual('user2');
done();
});
});
it('should be secure (#3835)', (done) => {
const acl = new Parse.ACL();
acl.getPublicReadAccess(true);
const role = new Parse.Role('admin', acl);
role.save().then(() => {
const user = new Parse.User();
return user.signUp({username: 'hello', password: 'world'});
}).then((user) => {
role.getUsers().add(user)
return role.save();
}).then(done.fail, () => {
const query = role.getUsers().query();
return query.find({useMasterKey: true});
}).then((results) => {
expect(results.length).toBe(0);
done();
})
.catch(done.fail);
});
it('should match when matching in users relation', (done) => {
var user = new Parse.User();
user
.save({ username: 'admin', password: 'admin' })
.then((user) => {
var aCL = new Parse.ACL();
aCL.setPublicReadAccess(true);
aCL.setPublicWriteAccess(true);
var role = new Parse.Role('admin', aCL);
var users = role.relation('users');
users.add(user);
role
.save({}, { useMasterKey: true })
.then(() => {
var query = new Parse.Query(Parse.Role);
query.equalTo('name', 'admin');
query.equalTo('users', user);
query.find().then(function (roles) {
expect(roles.length).toEqual(1);
done();
});
});
});
});
it('should not match any entry when not matching in users relation', (done) => {
var user = new Parse.User();
user
.save({ username: 'admin', password: 'admin' })
.then((user) => {
var aCL = new Parse.ACL();
aCL.setPublicReadAccess(true);
aCL.setPublicWriteAccess(true);
var role = new Parse.Role('admin', aCL);
var users = role.relation('users');
users.add(user);
role
.save({}, { useMasterKey: true })
.then(() => {
var otherUser = new Parse.User();
otherUser
.save({ username: 'otherUser', password: 'otherUser' })
.then((otherUser) => {
var query = new Parse.Query(Parse.Role);
query.equalTo('name', 'admin');
query.equalTo('users', otherUser);
query.find().then(function(roles) {
expect(roles.length).toEqual(0);
done();
});
});
});
});
});
it('should not match any entry when searching for null in users relation', (done) => {
var user = new Parse.User();
user
.save({ username: 'admin', password: 'admin' })
.then((user) => {
var aCL = new Parse.ACL();
aCL.setPublicReadAccess(true);
aCL.setPublicWriteAccess(true);
var role = new Parse.Role('admin', aCL);
var users = role.relation('users');
users.add(user);
role
.save({}, { useMasterKey: true })
.then(() => {
var query = new Parse.Query(Parse.Role);
query.equalTo('name', 'admin');
query.equalTo('users', null);
query.find().then(function (roles) {
expect(roles.length).toEqual(0);
done();
});
});
});
});
});
+68 -32
View File
@@ -1,35 +1,37 @@
const ParseServerRESTController = require('../src/ParseServerRESTController').ParseServerRESTController;
const ParseServer = require('../src/ParseServer').default;
const Parse = require('parse/node').Parse;
let RESTController;
describe('ParseServerRESTController', () => {
describe('ParseServerRESTController', () => {
beforeEach(() => {
beforeEach(() => {
RESTController = ParseServerRESTController(Parse.applicationId, ParseServer.promiseRouter({appId: Parse.applicationId}));
})
it('should handle a get request', (done) => {
RESTController.request("GET", "/classes/MyObject").then((res) => {
it('should handle a get request', (done) => {
RESTController.request("GET", "/classes/MyObject").then((res) => {
expect(res.results.length).toBe(0);
done();
}, (err) => {
}, (err) => {
console.log(err);
jfail(err);
done();
});
});
it('should handle a get request with full serverURL mount path', (done) => {
RESTController.request("GET", "/1/classes/MyObject").then((res) => {
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) => {
}, (err) => {
jfail(err);
done();
});
});
it('should handle a POST batch', (done) => {
it('should handle a POST batch', (done) => {
RESTController.request("POST", "batch", {
requests: [
{
@@ -46,74 +48,108 @@ describe('ParseServerRESTController', () => {
path: '/classes/MyObject'
}
]
}).then((res) => {
}).then((res) => {
expect(res.length).toBe(3);
done();
}, (err) => {
}, (err) => {
jfail(err);
done();
});
});
it('should handle a POST request', (done) => {
RESTController.request("POST", "/classes/MyObject", {"key": "value"}).then((res) => {
it('should handle a POST request', (done) => {
RESTController.request("POST", "/classes/MyObject", {"key": "value"}).then(() => {
return RESTController.request("GET", "/classes/MyObject");
}).then((res) => {
}).then((res) => {
expect(res.results.length).toBe(1);
expect(res.results[0].key).toEqual("value");
done();
}).fail((err) => {
}).fail((err) => {
console.log(err);
jfail(err);
done();
});
});
it('ensures sessionTokens are properly handled', (done) => {
it('ensures sessionTokens are properly handled', (done) => {
let userId;
Parse.User.signUp('user', 'pass').then((user) => {
Parse.User.signUp('user', 'pass').then((user) => {
userId = user.id;
let sessionToken = user.getSessionToken();
const sessionToken = user.getSessionToken();
return RESTController.request("GET", "/users/me", undefined, {sessionToken});
}).then((res) => {
}).then((res) => {
// Result is in JSON format
expect(res.objectId).toEqual(userId);
done();
}).fail((err) => {
}).fail((err) => {
console.log(err);
jfail(err);
done();
});
});
it('ensures masterKey is properly handled', (done) => {
it('ensures masterKey is properly handled', (done) => {
let userId;
Parse.User.signUp('user', 'pass').then((user) => {
Parse.User.signUp('user', 'pass').then((user) => {
userId = user.id;
let sessionToken = user.getSessionToken();
return Parse.User.logOut().then(() => {
return Parse.User.logOut().then(() => {
return RESTController.request("GET", "/classes/_User", undefined, {useMasterKey: true});
});
}).then((res) => {
}).then((res) => {
expect(res.results.length).toBe(1);
expect(res.results[0].objectId).toEqual(userId);
done();
}, (err) => {
}, (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(() => {
let query = new Parse.Query('_Session');
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 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 => {
}).then(sessions => {
expect(sessions.length).toBe(0);
done();
}, (err) => {
}, 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();
});
});
});
});
+770 -353
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -14,7 +14,9 @@ describe('ParseWebSocketServer', function() {
it('can handle connect event when ws is open', function(done) {
var onConnectCallback = jasmine.createSpy('onConnectCallback');
var parseWebSocketServer = new ParseWebSocketServer({}, onConnectCallback, 5).server;
var http = require('http');
var server = http.createServer();
var parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
var ws = {
readyState: 0,
OPEN: 0,
@@ -27,6 +29,7 @@ describe('ParseWebSocketServer', function() {
// Make sure we ping to the client
setTimeout(function() {
expect(ws.ping).toHaveBeenCalled();
server.close();
done();
}, 10)
});
File diff suppressed because it is too large Load Diff
+253 -218
View File
@@ -1,18 +1,16 @@
'use strict';
var Schema = require('../src/Controllers/SchemaController');
var Config = require('../src/Config');
describe('Pointer Permissions', () => {
describe('Pointer Permissions', () => {
beforeEach(() => {
new Config(Parse.applicationId).database.schemaCache.clear();
});
it('should work with find', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
it('should work with find', (done) => {
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -21,37 +19,37 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
let obj2 = new Parse.Object('AnObject');
const obj = new Parse.Object('AnObject');
const obj2 = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
obj.set('owner', user);
obj2.set('owner', user2);
return Parse.Object.saveAll([obj, obj2]);
}).then(() => {
}).then(() => {
return config.database.loadSchema().then((schema) => {
return schema.updateClass('AnObject', {}, {readUserFields: ['owner']})
});
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
let q = new Parse.Query('AnObject');
return Parse.User.logIn('user1', 'password');
}).then(() => {
const q = new Parse.Query('AnObject');
return q.find();
}).then((res) => {
expect(res.length).toBe(1);
expect(res[0].id).toBe(obj.id);
done();
}).catch(error => {
}).catch(error => {
fail(JSON.stringify(error));
done();
});
});
it('should work with write', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
it('should work with write', (done) => {
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -60,8 +58,8 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
let obj2 = new Parse.Object('AnObject');
const obj = new Parse.Object('AnObject');
const obj2 = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
obj.set('owner', user);
@@ -69,16 +67,16 @@ describe('Pointer Permissions', () => {
obj2.set('owner', user2);
obj2.set('reader', user);
return Parse.Object.saveAll([obj, obj2]);
}).then(() => {
}).then(() => {
return config.database.loadSchema().then((schema) => {
return schema.updateClass('AnObject', {}, {writeUserFields: ['owner'], readUserFields: ['reader', 'owner']});
});
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
obj2.set('hello', 'world');
return obj2.save();
}).then((res) => {
}).then(() => {
fail('User should not be able to update obj2');
}, (err) => {
// User 1 should not be able to update obj2
@@ -87,19 +85,19 @@ describe('Pointer Permissions', () => {
}).then(()=> {
obj.set('hello', 'world');
return obj.save();
}).then(() => {
}).then(() => {
return Parse.User.logIn('user2', 'password');
}, (err) => {
}, () => {
fail('User should be able to update');
return Promise.resolve();
}).then(() => {
let q = new Parse.Query('AnObject');
}).then(() => {
const q = new Parse.Query('AnObject');
return q.find();
}, (err) => {
}, () => {
fail('should login with user 2');
}).then((res) => {
}).then((res) => {
expect(res.length).toBe(2);
res.forEach((result) => {
res.forEach((result) => {
if (result.id == obj.id) {
expect(result.get('hello')).toBe('world');
} else {
@@ -107,16 +105,16 @@ describe('Pointer Permissions', () => {
}
})
done();
}, (err) =>  {
}, () => {
fail("failed");
done();
})
});
it('should let a proper user find', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -125,84 +123,58 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
let obj2 = new Parse.Object('AnObject');
const obj = new Parse.Object('AnObject');
const obj2 = new Parse.Object('AnObject');
user.signUp().then(() => {
return user2.signUp()
}).then(() => {
}).then(() => {
Parse.User.logOut();
}).then(() => {
obj.set('owner', user);
return Parse.Object.saveAll([obj, obj2]);
}).then(() => {
}).then(() => {
return config.database.loadSchema().then((schema) => {
return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
});
}).then(() => {
let q = new Parse.Query('AnObject');
}).then(() => {
const q = new Parse.Query('AnObject');
return q.find();
}).then((res) => {
}).then((res) => {
expect(res.length).toBe(0);
}).then(() => {
return Parse.User.logIn('user2', 'password');
}).then(() => {
let q = new Parse.Query('AnObject');
const q = new Parse.Query('AnObject');
return q.find();
}).then((res) => {
}).then((res) => {
expect(res.length).toBe(0);
let q = new Parse.Query('AnObject');
const q = new Parse.Query('AnObject');
return q.get(obj.id);
}).then(() => {
fail('User 2 should not get the obj1 object');
}, (err) => {
}, (err) => {
expect(err.code).toBe(101);
expect(err.message).toBe('Object not found.');
return Promise.resolve();
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
let q = new Parse.Query('AnObject');
const q = new Parse.Query('AnObject');
return q.find();
}).then((res) => {
}).then((res) => {
expect(res.length).toBe(1);
done();
}).catch((err) => {
}).catch((err) => {
jfail(err);
fail('should not fail');
done();
})
});
it('should not allow creating objects', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
user.set({
username: 'user1',
password: 'password'
});
let obj = new Parse.Object('AnObject');
user.save().then(() => {
return config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {owner: {type:'Pointer', targetClass: '_User'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
});
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
obj.set('owner', user);
return obj.save();
}).then(() => {
fail('should not succeed');
done();
}, (err) => {
expect(err.code).toBe(119);
done();
})
});
it('should handle multiple writeUserFields', done => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
it('should query on pointer permission enabled column', (done) => {
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -211,77 +183,144 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2])
.then(() => {
const obj = new Parse.Object('AnObject');
const obj2 = new Parse.Object('AnObject');
user.signUp().then(() => {
return user2.signUp()
}).then(() => {
Parse.User.logOut();
}).then(() => {
obj.set('owner', user);
obj.set('otherOwner', user2);
return obj.save();
})
.then(() => config.database.loadSchema())
.then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
.then(() => Parse.User.logIn('user1', 'password'))
.then(() => obj.save({hello: 'fromUser1'}))
.then(() => Parse.User.logIn('user2', 'password'))
.then(() => obj.save({hello: 'fromUser2'}))
.then(() => Parse.User.logOut())
.then(() => {
let q = new Parse.Query('AnObject');
return q.first();
})
.then(result => {
expect(result.get('hello')).toBe('fromUser2');
return Parse.Object.saveAll([obj, obj2]);
}).then(() => {
return config.database.loadSchema().then((schema) => {
return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
});
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
const q = new Parse.Query('AnObject');
q.equalTo('owner', user2);
return q.find();
}).then((res) => {
expect(res.length).toBe(0);
done();
}).catch(err => {
}).catch((err) => {
jfail(err);
fail('should not fail');
done();
})
});
it('should prevent creating pointer permission on missing field', (done) => {
let config = new Config(Parse.applicationId);
it('should not allow creating objects', (done) => {
const config = new Config(Parse.applicationId);
const user = new Parse.User();
user.set({
username: 'user1',
password: 'password'
});
const obj = new Parse.Object('AnObject');
user.save().then(() => {
return config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {owner: {type:'Pointer', targetClass: '_User'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
});
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
obj.set('owner', user);
return obj.save();
}).then(() => {
fail('should not succeed');
done();
}, (err) => {
expect(err.code).toBe(119);
done();
})
});
it('should handle multiple writeUserFields', done => {
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
});
user2.set({
username: 'user2',
password: 'password'
});
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2])
.then(() => {
obj.set('owner', user);
obj.set('otherOwner', user2);
return obj.save();
})
.then(() => config.database.loadSchema())
.then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
.then(() => Parse.User.logIn('user1', 'password'))
.then(() => obj.save({hello: 'fromUser1'}))
.then(() => Parse.User.logIn('user2', 'password'))
.then(() => obj.save({hello: 'fromUser2'}))
.then(() => Parse.User.logOut())
.then(() => {
const q = new Parse.Query('AnObject');
return q.first();
})
.then(result => {
expect(result.get('hello')).toBe('fromUser2');
done();
}).catch(() => {
fail('should not fail');
done();
})
});
it('should prevent creating pointer permission on missing field', (done) => {
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
}).then(() => {
fail('should not succeed');
}).catch((err) => {
}).catch((err) => {
expect(err.code).toBe(107);
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
done();
})
});
it('should prevent creating pointer permission on bad field', (done) => {
let config = new Config(Parse.applicationId);
it('should prevent creating pointer permission on bad field', (done) => {
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {owner: {type: 'String'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
}).then(() => {
fail('should not succeed');
}).catch((err) => {
}).catch((err) => {
expect(err.code).toBe(107);
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
done();
})
});
it('should prevent creating pointer permission on bad field', (done) => {
let config = new Config(Parse.applicationId);
let object = new Parse.Object('AnObject');
it('should prevent creating pointer permission on bad field', (done) => {
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('owner', 'value');
object.save().then(() => {
object.save().then(() => {
return config.database.loadSchema();
}).then((schema) => {
return schema.updateClass('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
}).then(() => {
fail('should not succeed');
}).catch((err) => {
}).catch((err) => {
expect(err.code).toBe(107);
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
done();
})
});
it('tests CLP / Pointer Perms / ACL write (PP Locked)', (done) => {
it('tests CLP / Pointer Perms / ACL write (PP Locked)', (done) => {
/*
tests:
CLP: update closed ({})
@@ -290,9 +329,9 @@ describe('Pointer Permissions', () => {
The owner is another user than the ACL
*/
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -301,9 +340,9 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
let ACL = new Parse.ACL();
const ACL = new Parse.ACL();
ACL.setReadAccess(user, true);
ACL.setWriteAccess(user, true);
obj.setACL(ACL);
@@ -314,30 +353,30 @@ describe('Pointer Permissions', () => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
});
}).then(() => {
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
}).then(() => {
// user1 has ACL read/write but should be blocked by PP
return obj.save({key: 'value'});
}).then(() => {
}).then(() => {
fail('Should not succeed saving');
done();
}, (err) => {
}, (err) => {
expect(err.code).toBe(101);
done();
});
});
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', (done) => {
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', (done) => {
/*
tests:
CLP: update closed ({})
PointerPerm: "owner"
ACL: logged in user has access
*/
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -346,9 +385,9 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
let ACL = new Parse.ACL();
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
const ACL = new Parse.ACL();
ACL.setReadAccess(user, true);
ACL.setWriteAccess(user, true);
obj.setACL(ACL);
@@ -359,30 +398,30 @@ describe('Pointer Permissions', () => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
});
}).then(() => {
}).then(() => {
return Parse.User.logIn('user2', 'password');
}).then(() => {
}).then(() => {
// user1 has ACL read/write but should be blocked by ACL
return obj.save({key: 'value'});
}).then(() => {
}).then(() => {
fail('Should not succeed saving');
done();
}, (err) => {
}, (err) => {
expect(err.code).toBe(101);
done();
});
});
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', (done) => {
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', (done) => {
/*
tests:
CLP: update closed ({})
PointerPerm: "owner"
ACL: logged in user has access
*/
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -391,9 +430,9 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
let ACL = new Parse.ACL();
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
const ACL = new Parse.ACL();
ACL.setWriteAccess(user, true);
ACL.setWriteAccess(user2, true);
obj.setACL(ACL);
@@ -404,21 +443,21 @@ describe('Pointer Permissions', () => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
});
}).then(() => {
}).then(() => {
return Parse.User.logIn('user2', 'password');
}).then(() => {
}).then(() => {
// user1 has ACL read/write but should be blocked by ACL
return obj.save({key: 'value'});
}).then((objAgain) => {
}).then((objAgain) => {
expect(objAgain.get('key')).toBe('value');
done();
}, (err) => {
}, () => {
fail('Should not fail saving');
done();
});
});
it('tests CLP / Pointer Perms / ACL read (PP locked)', (done) => {
it('tests CLP / Pointer Perms / ACL read (PP locked)', (done) => {
/*
tests:
CLP: find/get open ({})
@@ -427,9 +466,9 @@ describe('Pointer Permissions', () => {
The owner is another user than the ACL
*/
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -438,9 +477,9 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
let ACL = new Parse.ACL();
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
const ACL = new Parse.ACL();
ACL.setReadAccess(user, true);
ACL.setWriteAccess(user, true);
obj.setACL(ACL);
@@ -451,30 +490,30 @@ describe('Pointer Permissions', () => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {}, {find: {}, get: {}, readUserFields: ['owner']});
});
}).then(() => {
}).then(() => {
return Parse.User.logIn('user1', 'password');
}).then(() => {
}).then(() => {
// user1 has ACL read/write but should be block
return obj.fetch();
}).then(() => {
}).then(() => {
fail('Should not succeed saving');
done();
}, (err) => {
}, (err) => {
expect(err.code).toBe(101);
done();
});
});
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', (done) => {
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', (done) => {
/*
tests:
CLP: find/get open ({"*": true})
PointerPerm: "owner" : read
ACL: logged in user has access
*/
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -483,9 +522,9 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
let ACL = new Parse.ACL();
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
const ACL = new Parse.ACL();
ACL.setReadAccess(user, true);
ACL.setWriteAccess(user, true);
ACL.setReadAccess(user2, true);
@@ -498,30 +537,30 @@ describe('Pointer Permissions', () => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
});
}).then(() => {
}).then(() => {
return Parse.User.logIn('user2', 'password');
}).then(() => {
}).then(() => {
// user1 has ACL read/write but should be block
return obj.fetch();
}).then((objAgain) => {
}).then((objAgain) => {
expect(objAgain.id).toBe(obj.id);
done();
}, (err) => {
}, () => {
fail('Should not fail fetching');
done();
});
});
it('tests CLP / Pointer Perms / ACL read (ACL locked)', (done) => {
it('tests CLP / Pointer Perms / ACL read (ACL locked)', (done) => {
/*
tests:
CLP: find/get open ({"*": true})
PointerPerm: "owner" : read // proper owner
ACL: logged in user has not access
*/
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let user2 = new Parse.User();
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
username: 'user1',
password: 'password'
@@ -530,9 +569,9 @@ describe('Pointer Permissions', () => {
username: 'user2',
password: 'password'
});
let obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
let ACL = new Parse.ACL();
const obj = new Parse.Object('AnObject');
Parse.Object.saveAll([user, user2]).then(() => {
const ACL = new Parse.ACL();
ACL.setReadAccess(user, true);
ACL.setWriteAccess(user, true);
obj.setACL(ACL);
@@ -543,62 +582,60 @@ describe('Pointer Permissions', () => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
});
}).then(() => {
}).then(() => {
return Parse.User.logIn('user2', 'password');
}).then(() => {
}).then(() => {
// user2 has ACL read/write but should be block by ACL
return obj.fetch();
}).then(() => {
}).then(() => {
fail('Should not succeed saving');
done();
}, (err) => {
}, (err) => {
expect(err.code).toBe(101);
done();
});
});
it('should let master key find objects', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let object = new Parse.Object('AnObject');
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
return object.save().then(() => {
return config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
});
}).then(() => {
let q = new Parse.Query('AnObject');
const q = new Parse.Query('AnObject');
return q.find();
}).then(() => {
}, (err) => {
expect(err.code).toBe(101);
return Promise.resolve();
}).then(() => {
let q = new Parse.Query('AnObject');
}).then(() => {
const q = new Parse.Query('AnObject');
return q.find({useMasterKey: true});
}).then((objects) => {
expect(objects.length).toBe(1);
done();
}, (err) => {
}, () => {
fail('master key should find the object');
done();
})
});
it('should let master key get objects', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let object = new Parse.Object('AnObject');
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
return object.save().then(() => {
return config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
});
}).then(() => {
let q = new Parse.Query('AnObject');
const q = new Parse.Query('AnObject');
return q.get(object.id);
}).then(() => {
@@ -606,13 +643,13 @@ describe('Pointer Permissions', () => {
expect(err.code).toBe(101);
return Promise.resolve();
}).then(() => {
let q = new Parse.Query('AnObject');
const q = new Parse.Query('AnObject');
return q.get(object.id, {useMasterKey: true});
}).then((objectAgain) => {
expect(objectAgain).not.toBeUndefined();
expect(objectAgain.id).toBe(object.id);
done();
}, (err) => {
}, () => {
fail('master key should find the object');
done();
})
@@ -620,11 +657,10 @@ describe('Pointer Permissions', () => {
it('should let master key update objects', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let object = new Parse.Object('AnObject');
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
return object.save().then(() => {
return config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {update: {}, writeUserFields: ['owner']});
@@ -636,23 +672,22 @@ describe('Pointer Permissions', () => {
}, (err) => {
expect(err.code).toBe(101);
return Promise.resolve();
}).then(() => {
}).then(() => {
return object.save({'hello': 'baz'}, {useMasterKey: true});
}).then((objectAgain) => {
expect(objectAgain.get('hello')).toBe('baz');
done();
}, (err) => {
}, () => {
fail('master key should save the object');
done();
})
});
it('should let master key delete objects', (done) => {
let config = new Config(Parse.applicationId);
let user = new Parse.User();
let object = new Parse.Object('AnObject');
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
return object.save().then(() => {
return config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner']});
@@ -664,35 +699,35 @@ describe('Pointer Permissions', () => {
}, (err) => {
expect(err.code).toBe(101);
return Promise.resolve();
}).then(() => {
}).then(() => {
return object.destroy({useMasterKey: true});
}).then((objectAgain) => {
}).then(() => {
done();
}, (err) => {
}, () => {
fail('master key should destroy the object');
done();
})
});
it('should fail with invalid pointer perms', () => {
let config = new Config(Parse.applicationId);
it('should fail with invalid pointer perms', (done) => {
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
}).catch((err) => {
// Lock the update, and let only owner write
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
}).catch((err) => {
expect(err.code).toBe(Parse.Error.INVALID_JSON);
done();
});
});
});
it('should fail with invalid pointer perms', () => {
let config = new Config(Parse.applicationId);
it('should fail with invalid pointer perms', (done) => {
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});
}).catch((err) => {
// Lock the update, and let only owner write
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});
}).catch((err) => {
expect(err.code).toBe(Parse.Error.INVALID_JSON);
done();
});
});
})
});
+82
View File
@@ -0,0 +1,82 @@
const Parse = require('parse/node').Parse;
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
const ParseServer = require("../src/index");
const express = require('express');
//public schema
const databaseOptions1 = {
initOptions: {
connect: function (client, dc, isFresh) {
if (isFresh) {
client.query('SET search_path = public');
}
}
}
};
//not exists schema
const databaseOptions2 = {
initOptions: {
connect: function (client, dc, isFresh) {
if (isFresh) {
client.query('SET search_path = not_exists_schema');
}
}
}
};
const GameScore = Parse.Object.extend({
className: "GameScore"
});
function createParseServer(options) {
return new Promise((resolve, reject) => {
const parseServer = new ParseServer.default(Object.assign({},
defaultConfiguration, options, {
serverURL: "http://localhost:12666/parse",
__indexBuildCompletionCallbackForTests: promise => {
promise
.then(() => {
expect(Parse.applicationId).toEqual("test");
var app = express();
app.use('/parse', parseServer.app);
const server = app.listen(12666);
Parse.serverURL = "http://localhost:12666/parse";
resolve(server);
}, reject);
}}));
});
}
describe_only_db('postgres')('Postgres database init options', () => {
let server;
afterEach(() => {
if (server) {
server.close();
}
})
it('should create server with public schema databaseOptions', (done) => {
const adapter = new PostgresStorageAdapter({
uri: postgresURI, collectionPrefix: 'test_',
databaseOptions: databaseOptions1
})
createParseServer({ databaseAdapter: adapter }).then((newServer) => {
server = newServer;
var score = new GameScore({ "score": 1337, "playerName": "Sean Plott", "cheatMode": false });
return score.save();
}).then(done, done.fail);
});
it('should fail to create server if schema databaseOptions does not exist', (done) => {
const adapter = new PostgresStorageAdapter({
uri: postgresURI, collectionPrefix: 'test_',
databaseOptions: databaseOptions2
})
createParseServer({ databaseAdapter: adapter }).then(done.fail, done);
});
});
+4 -4
View File
@@ -1,14 +1,14 @@
var PromiseRouter = require("../src/PromiseRouter").default;
describe("PromiseRouter", () => {
it("should properly handle rejects", (done) => {
describe("PromiseRouter", () => {
it("should properly handle rejects", (done) => {
var router = new PromiseRouter();
router.route("GET", "/dummy", (req)=> {
router.route("GET", "/dummy", ()=> {
return Promise.reject({
error: "an error",
code: -1
})
}, (req) => {
}, () => {
fail("this should not be called");
});
+14 -14
View File
@@ -3,7 +3,7 @@ var request = require('request');
describe("public API", () => {
it("should get invalid_link.html", (done) => {
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse, body) => {
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(200);
done();
});
@@ -14,23 +14,23 @@ describe("public API", () => {
appName: 'unused',
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
expect(httpResponse.statusCode).toBe(200);
done();
});
})
.then(() => {
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(200);
done();
});
})
});
it("should get verify_email_success.html", (done) => {
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse, body) => {
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(200);
done();
});
});
it("should get password_reset_success.html", (done) => {
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse, body) => {
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(200);
done();
});
@@ -38,26 +38,26 @@ describe("public API", () => {
});
describe("public API without publicServerURL", () => {
beforeEach(done => {
beforeEach(done => {
reconfigureServer({ appName: 'unused' })
.then(done, fail);
.then(done, fail);
});
it("should get 404 on verify_email", (done) => {
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse, body) => {
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(404);
done();
});
});
it("should get 404 choose_password", (done) => {
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(404);
done();
});
});
it("should get 404 on request_password_reset", (done) => {
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse, body) => {
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(404);
done();
});
+48 -48
View File
@@ -20,15 +20,15 @@ function createProduct() {
}
describe("test validate_receipt endpoint", () => {
beforeEach( done => {
createProduct().then(done).fail(function(err){
beforeEach(done => {
createProduct().then(done).fail(function(){
done();
});
})
it("should bypass appstore validation", (done) => {
request.post({
request.post({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'},
@@ -45,7 +45,7 @@ describe("test validate_receipt endpoint", () => {
}, function(err, res, body){
if (typeof body != "object") {
fail("Body is not an object");
done();
done();
} else {
expect(body.__type).toEqual("File");
const url = body.url;
@@ -60,7 +60,7 @@ describe("test validate_receipt endpoint", () => {
});
it("should fail for missing receipt", (done) => {
request.post({
request.post({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'},
@@ -73,7 +73,7 @@ describe("test validate_receipt endpoint", () => {
}, function(err, res, body){
if (typeof body != "object") {
fail("Body is not an object");
done();
done();
} else {
expect(body.code).toEqual(Parse.Error.INVALID_JSON);
done();
@@ -82,7 +82,7 @@ describe("test validate_receipt endpoint", () => {
});
it("should fail for missing product identifier", (done) => {
request.post({
request.post({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'},
@@ -98,7 +98,7 @@ describe("test validate_receipt endpoint", () => {
}, function(err, res, body){
if (typeof body != "object") {
fail("Body is not an object");
done();
done();
} else {
expect(body.code).toEqual(Parse.Error.INVALID_JSON);
done();
@@ -108,7 +108,7 @@ describe("test validate_receipt endpoint", () => {
it("should bypass appstore validation and not find product", (done) => {
request.post({
request.post({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'},
@@ -135,7 +135,7 @@ describe("test validate_receipt endpoint", () => {
});
it("should fail at appstore validation", done => {
request.post({
request.post({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'},
@@ -160,49 +160,49 @@ describe("test validate_receipt endpoint", () => {
});
it("should not create a _Product", (done) => {
var product = new Parse.Object("_Product");
product.save().then(function(){
fail("Should not be able to save");
done();
}, function(err){
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
done();
})
var product = new Parse.Object("_Product");
product.save().then(function(){
fail("Should not be able to save");
done();
}, function(err){
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
done();
})
});
it("should be able to update a _Product", (done) => {
var query = new Parse.Query("_Product");
query.first().then(function(product) {
if (!product) {
return Promise.reject(new Error('Product should be found'));
}
product.set("title", "a new title");
return product.save();
}).then(function(productAgain){
expect(productAgain.get('downloadName')).toEqual(productAgain.get('download').name());
expect(productAgain.get("title")).toEqual("a new title");
done();
}).fail(function(err){
fail(JSON.stringify(err));
done();
});
var query = new Parse.Query("_Product");
query.first().then(function(product) {
if (!product) {
return Promise.reject(new Error('Product should be found'));
}
product.set("title", "a new title");
return product.save();
}).then(function(productAgain){
expect(productAgain.get('downloadName')).toEqual(productAgain.get('download').name());
expect(productAgain.get("title")).toEqual("a new title");
done();
}).fail(function(err){
fail(JSON.stringify(err));
done();
});
});
it("should not be able to remove a require key in a _Product", (done) => {
var query = new Parse.Query("_Product");
query.first().then(function(product){
if (!product) {
return Promise.reject(new Error('Product should be found'));
}
product.unset("title");
return product.save();
}).then(function(productAgain){
fail("Should not succeed");
done();
}).fail(function(err){
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
expect(err.message).toEqual("title is required.");
done();
});
var query = new Parse.Query("_Product");
query.first().then(function(product){
if (!product) {
return Promise.reject(new Error('Product should be found'));
}
product.unset("title");
return product.save();
}).then(function(){
fail("Should not succeed");
done();
}).fail(function(err){
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
expect(err.message).toEqual("title is required.");
done();
});
});
});
+1041 -285
View File
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
import Config from "../src/Config";
import {PushQueue} from "../src/Push/PushQueue";
describe('PushQueue', () => {
describe('With a defined channel', () => {
it('should be propagated to the PushWorker and PushQueue', (done) => {
reconfigureServer({
push: {
queueOptions: {
disablePushWorker: false,
channel: 'my-specific-channel'
},
adapter: {
send() {
return Promise.resolve();
},
getValidPushTypes() {
return [];
}
}
}
})
.then(() => {
const config = new Config(Parse.applicationId);
expect(config.pushWorker.channel).toEqual('my-specific-channel', 'pushWorker.channel');
expect(config.pushControllerQueue.channel).toEqual('my-specific-channel', 'pushWorker.channel');
})
.then(done, done.fail);
});
});
describe('Default channel', () => {
it('should be prefixed with the applicationId', (done) => {
reconfigureServer({
push: {
queueOptions: {
disablePushWorker: false
},
adapter: {
send() {
return Promise.resolve();
},
getValidPushTypes() {
return [];
}
}
}
})
.then(() => {
const config = new Config(Parse.applicationId);
expect(PushQueue.defaultPushChannel()).toEqual('test-parse-server-push');
expect(config.pushWorker.channel).toEqual('test-parse-server-push');
expect(config.pushControllerQueue.channel).toEqual('test-parse-server-push');
})
.then(done, done.fail);
});
});
});
+1 -1
View File
@@ -70,7 +70,7 @@ describe('PushRouter', () => {
it('sends a push through REST', (done) => {
request.post({
url: Parse.serverURL+"/push",
url: Parse.serverURL + "/push",
json: true,
body: {
'channels': {
+366
View File
@@ -0,0 +1,366 @@
var PushWorker = require('../src').PushWorker;
var PushUtils = require('../src/Push/utils');
var Config = require('../src/Config');
var { pushStatusHandler } = require('../src/StatusHandler');
describe('PushWorker', () => {
it('should run with small batch', (done) => {
const batchSize = 3;
var sendCount = 0;
reconfigureServer({
push: {
queueOptions: {
disablePushWorker: true,
batchSize
}
}
}).then(() => {
expect(new Config('test').pushWorker).toBeUndefined();
new PushWorker({
send: (body, installations) => {
expect(installations.length <= batchSize).toBe(true);
sendCount += installations.length;
return Promise.resolve();
},
getValidPushTypes: function() {
return ['ios', 'android']
}
});
var installations = [];
while(installations.length != 10) {
var installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", 1);
installation.set("deviceType", "ios");
installations.push(installation);
}
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send({
where: {
deviceType: 'ios'
},
data: {
alert: 'Hello world!'
}
}, {useMasterKey: true})
}).then(() => {
return new Promise((resolve) => {
setTimeout(resolve, 500);
});
}).then(() => {
expect(sendCount).toBe(10);
done();
}).catch(err => {
jfail(err);
})
});
describe('localized push', () => {
it('should return locales', () => {
const locales = PushUtils.getLocalesFromPush({
data: {
'alert-fr': 'french',
'alert': 'Yo!',
'alert-en-US': 'English',
}
});
expect(locales).toEqual(['fr', 'en-US']);
});
it('should return and empty array if no locale is set', () => {
const locales = PushUtils.getLocalesFromPush({
data: {
'alert': 'Yo!',
}
});
expect(locales).toEqual([]);
});
it('should deduplicate locales', () => {
const locales = PushUtils.getLocalesFromPush({
data: {
'alert': 'Yo!',
'alert-fr': 'french',
'title-fr': 'french'
}
});
expect(locales).toEqual(['fr']);
});
it('transforms body appropriately', () => {
const cleanBody = PushUtils.transformPushBodyForLocale({
data: {
alert: 'Yo!',
'alert-fr': 'frenchy!',
'alert-en': 'english',
}
}, 'fr');
expect(cleanBody).toEqual({
data: {
alert: 'frenchy!'
}
});
});
it('transforms body appropriately', () => {
const cleanBody = PushUtils.transformPushBodyForLocale({
data: {
alert: 'Yo!',
'alert-fr': 'frenchy!',
'alert-en': 'english',
'title-fr': 'french title'
}
}, 'fr');
expect(cleanBody).toEqual({
data: {
alert: 'frenchy!',
title: 'french title'
}
});
});
it('maps body on all provided locales', () => {
const bodies = PushUtils.bodiesPerLocales({
data: {
alert: 'Yo!',
'alert-fr': 'frenchy!',
'alert-en': 'english',
'title-fr': 'french title'
}
}, ['fr', 'en']);
expect(bodies).toEqual({
fr: {
data: {
alert: 'frenchy!',
title: 'french title'
}
},
en: {
data: {
alert: 'english',
}
},
default: {
data: {
alert: 'Yo!'
}
}
});
});
it('should properly handle default cases', () => {
expect(PushUtils.transformPushBodyForLocale({})).toEqual({});
expect(PushUtils.stripLocalesFromBody({})).toEqual({});
expect(PushUtils.bodiesPerLocales({where: {}})).toEqual({default: {where: {}}});
expect(PushUtils.groupByLocaleIdentifier([])).toEqual({default: []});
});
});
describe('pushStatus', () => {
it('should remove invalid installations', (done) => {
const config = new Config('test');
const handler = pushStatusHandler(config);
const spy = spyOn(config.database, "update").and.callFake(() => {
return Promise.resolve();
});
const toAwait = handler.trackSent([
{
transmitted: false,
device: {
deviceToken: 1,
deviceType: 'ios',
},
response: { error: 'Unregistered' }
},
{
transmitted: true,
device: {
deviceToken: 10,
deviceType: 'ios',
},
},
{
transmitted: false,
device: {
deviceToken: 2,
deviceType: 'ios',
},
response: { error: 'NotRegistered' }
},
{
transmitted: false,
device: {
deviceToken: 3,
deviceType: 'ios',
},
response: { error: 'InvalidRegistration' }
},
{
transmitted: true,
device: {
deviceToken: 11,
deviceType: 'ios',
},
},
{
transmitted: false,
device: {
deviceToken: 4,
deviceType: 'ios',
},
response: { error: 'InvalidRegistration' }
},
{
transmitted: false,
device: {
deviceToken: 5,
deviceType: 'ios',
},
response: { error: 'InvalidRegistration' }
},
{ // should not be deleted
transmitted: false,
device: {
deviceToken: 101,
deviceType: 'ios',
},
response: { error: 'invalid error...' }
}
], undefined, true);
expect(spy).toHaveBeenCalled();
expect(spy.calls.count()).toBe(1);
const lastCall = spy.calls.mostRecent();
expect(lastCall.args[0]).toBe('_Installation');
expect(lastCall.args[1]).toEqual({
deviceToken: { '$in': [1,2,3,4,5] }
});
expect(lastCall.args[2]).toEqual({
deviceToken: { '__op': "Delete" }
});
toAwait.then(done).catch(done);
});
it('tracks push status per UTC offsets', (done) => {
const config = new Config('test');
const handler = pushStatusHandler(config);
const spy = spyOn(Parse, "_request").and.callThrough();
const UTCOffset = 1;
handler.setInitial().then(() => {
return handler.trackSent([
{
transmitted: false,
device: {
deviceToken: 1,
deviceType: 'ios',
},
},
{
transmitted: true,
device: {
deviceToken: 1,
deviceType: 'ios',
}
},
], UTCOffset)
}).then(() => {
expect(spy).toHaveBeenCalled();
const lastCall = spy.calls.mostRecent();
expect(lastCall.args[0]).toBe('PUT');
expect(lastCall.args[1]).toBe(`classes/_PushStatus/${handler.objectId}`);
expect(lastCall.args[2]).toEqual({
numSent: { __op: 'Increment', amount: 1 },
numFailed: { __op: 'Increment', amount: 1 },
'sentPerType.ios': { __op: 'Increment', amount: 1 },
'failedPerType.ios': { __op: 'Increment', amount: 1 },
[`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
[`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
count: { __op: 'Increment', amount: -2 },
});
const query = new Parse.Query('_PushStatus');
return query.get(handler.objectId, { useMasterKey: true });
}).then((pushStatus) => {
const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
expect(sentPerUTCOffset['1']).toBe(1);
const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
expect(failedPerUTCOffset['1']).toBe(1);
return handler.trackSent([
{
transmitted: false,
device: {
deviceToken: 1,
deviceType: 'ios',
},
},
{
transmitted: true,
device: {
deviceToken: 1,
deviceType: 'ios',
}
},
{
transmitted: true,
device: {
deviceToken: 1,
deviceType: 'ios',
}
},
], UTCOffset)
}).then(() => {
const query = new Parse.Query('_PushStatus');
return query.get(handler.objectId, { useMasterKey: true });
}).then((pushStatus) => {
const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
expect(sentPerUTCOffset['1']).toBe(3);
const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
expect(failedPerUTCOffset['1']).toBe(2);
}).then(done).catch(done.fail);
});
it('tracks push status per UTC offsets with negative offsets', (done) => {
const config = new Config('test');
const handler = pushStatusHandler(config);
spyOn(config.database, "create").and.callFake(() => {
return Promise.resolve();
});
const spy = spyOn(Parse, "_request").and.callFake(() => {
return Promise.resolve();
});
const UTCOffset = -6;
handler.trackSent([
{
transmitted: false,
device: {
deviceToken: 1,
deviceType: 'ios',
},
response: { error: 'Unregistered' }
},
{
transmitted: true,
device: {
deviceToken: 1,
deviceType: 'ios',
},
response: { error: 'Unregistered' }
},
], UTCOffset).then(() => {
expect(spy).toHaveBeenCalled();
const lastCall = spy.calls.mostRecent();
expect(lastCall.args[1]).toBe(`classes/_PushStatus/${handler.objectId}`);
expect(lastCall.args[2]).toEqual({
numSent: { __op: 'Increment', amount: 1 },
numFailed: { __op: 'Increment', amount: 1 },
'sentPerType.ios': { __op: 'Increment', amount: 1 },
'failedPerType.ios': { __op: 'Increment', amount: 1 },
[`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
[`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
count: { __op: 'Increment', amount: -2 },
});
done();
});
});
});
});
+101 -1
View File
@@ -226,6 +226,23 @@ describe('matchesQuery', function() {
img.owner.objectId = 'U3';
expect(matchesQuery(img, q)).toBe(false);
// pointers in arrays
q = new Parse.Query('Image');
q.equalTo('owners', u);
img = {
className: 'Image',
objectId: 'I1',
owners: [{
className: '_User',
objectId: 'U2'
}]
};
expect(matchesQuery(img, q)).toBe(true);
img.owners[0].objectId = 'U3';
expect(matchesQuery(img, q)).toBe(false);
});
it('matches on inequalities', function() {
@@ -343,7 +360,16 @@ describe('matchesQuery', function() {
id: new Id('Checkin', 'C1'),
location: new Parse.GeoPoint(40, 40)
};
var ptUndefined = {
id: new Id('Checkin', 'C1')
};
var ptNull = {
id: new Id('Checkin', 'C1'),
location: null
};
expect(matchesQuery(pt, q)).toBe(true);
expect(matchesQuery(ptUndefined, q)).toBe(false);
expect(matchesQuery(ptNull, q)).toBe(false);
q = new Parse.Query('Checkin');
pt.location = new Parse.GeoPoint(40, 40);
@@ -367,6 +393,17 @@ describe('matchesQuery', function() {
name: 'Santa Clara'
};
var noLocation = {
id: new Id('Checkin', 'C2'),
name: 'Santa Clara'
};
var nullLocation = {
id: new Id('Checkin', 'C2'),
location: null,
name: 'Santa Clara'
};
var q = new Parse.Query('Checkin').withinGeoBox(
'location',
new Parse.GeoPoint(37.708813, -122.526398),
@@ -375,7 +412,8 @@ describe('matchesQuery', function() {
expect(matchesQuery(caltrainStation, q)).toBe(true);
expect(matchesQuery(santaClara, q)).toBe(false);
expect(matchesQuery(noLocation, q)).toBe(false);
expect(matchesQuery(nullLocation, q)).toBe(false);
// Invalid rectangles
q = new Parse.Query('Checkin').withinGeoBox(
'location',
@@ -395,4 +433,66 @@ describe('matchesQuery', function() {
expect(matchesQuery(caltrainStation, q)).toBe(false);
expect(matchesQuery(santaClara, q)).toBe(false);
});
it('matches on subobjects with dot notation', function() {
var message = {
id: new Id('Message', 'O1'),
text: "content",
status: {x: "read", y: "delivered"}
};
var q = new Parse.Query('Message');
q.equalTo("status.x", "read");
expect(matchesQuery(message, q)).toBe(true);
q = new Parse.Query('Message');
q.equalTo("status.z", "read");
expect(matchesQuery(message, q)).toBe(false);
q = new Parse.Query('Message');
q.equalTo("status.x", "delivered");
expect(matchesQuery(message, q)).toBe(false);
q = new Parse.Query('Message');
q.notEqualTo("status.x", "read");
expect(matchesQuery(message, q)).toBe(false);
q = new Parse.Query('Message');
q.notEqualTo("status.z", "read");
expect(matchesQuery(message, q)).toBe(true);
q = new Parse.Query('Message');
q.notEqualTo("status.x", "delivered");
expect(matchesQuery(message, q)).toBe(true);
q = new Parse.Query('Message');
q.exists("status.x");
expect(matchesQuery(message, q)).toBe(true);
q = new Parse.Query('Message');
q.exists("status.z");
expect(matchesQuery(message, q)).toBe(false);
q = new Parse.Query('Message');
q.exists("nonexistent.x");
expect(matchesQuery(message, q)).toBe(false);
q = new Parse.Query('Message');
q.doesNotExist("status.x");
expect(matchesQuery(message, q)).toBe(false);
q = new Parse.Query('Message');
q.doesNotExist("status.z");
expect(matchesQuery(message, q)).toBe(true);
q = new Parse.Query('Message');
q.doesNotExist("nonexistent.z");
expect(matchesQuery(message, q)).toBe(true);
q = new Parse.Query('Message');
q.equalTo("status.x", "read");
q.doesNotExist("status.y");
expect(matchesQuery(message, q)).toBe(false);
});
});
+748
View File
@@ -0,0 +1,748 @@
'use strict'
const Parse = require('parse/node');
const ReadPreference = require('mongodb').ReadPreference;
const rp = require('request-promise');
const Config = require("../src/Config");
describe_only_db('mongo')('Read preference option', () => {
it('should find in primary by default', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.PRIMARY);
done();
});
});
});
it('should change read preference in the beforeFind trigger', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY';
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change read preference in the beforeFind trigger even changing query', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.query.equalTo('boolKey', true);
req.readPreference = 'SECONDARY';
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(true);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change read preference in the beforeFind trigger even returning query', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY';
const otherQuery = new Parse.Query('MyObject');
otherQuery.equalTo('boolKey', true);
return otherQuery;
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(true);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change read preference in the beforeFind trigger even returning promise', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY';
const otherQuery = new Parse.Query('MyObject');
otherQuery.equalTo('boolKey', true);
return Promise.resolve(otherQuery);
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(true);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change read preference to PRIMARY_PREFERRED', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'PRIMARY_PREFERRED';
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.PRIMARY_PREFERRED);
done();
});
});
});
it('should change read preference to SECONDARY_PREFERRED', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY_PREFERRED';
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY_PREFERRED);
done();
});
});
});
it('should change read preference to NEAREST', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'NEAREST';
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.NEAREST);
done();
});
});
});
it('should change read preference for GET', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY';
});
const query = new Parse.Query('MyObject');
query.get(obj0.id).then((result) => {
expect(result.get('boolKey')).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change read preference for GET using API', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY';
});
rp({
method: 'GET',
uri: 'http://localhost:8378/1/classes/MyObject/' + obj0.id,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
},
json: true,
}).then(body => {
expect(body.boolKey).toBe(false);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject') >= 0) {
myObjectReadPreference = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change read preference for count', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject');
obj1.set('boolKey', true);
Parse.Object.saveAll([obj0, obj1]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'command').and.callThrough();
Parse.Cloud.beforeFind('MyObject', (req) => {
req.readPreference = 'SECONDARY';
});
const query = new Parse.Query('MyObject');
query.equalTo('boolKey', false);
query.count().then((result) => {
expect(result).toBe(1);
let myObjectReadPreference = null;
databaseAdapter.database.serverConfig.command.calls.all().forEach((call) => {
myObjectReadPreference = call.args[2].readPreference.preference;
});
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should find includes in primary by default', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject1');
obj1.set('boolKey', true);
obj1.set('myObject0', obj0);
const obj2 = new Parse.Object('MyObject2');
obj2.set('boolKey', false);
obj2.set('myObject1', obj1);
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject2', (req) => {
req.readPreference = 'SECONDARY';
});
const query = new Parse.Query('MyObject2');
query.equalTo('boolKey', false);
query.include('myObject1');
query.include('myObject1.myObject0');
query.find().then((results) => {
expect(results.length).toBe(1);
const firstResult = results[0];
expect(firstResult.get('boolKey')).toBe(false);
expect(firstResult.get('myObject1').get('boolKey')).toBe(true);
expect(firstResult.get('myObject1').get('myObject0').get('boolKey')).toBe(false);
let myObjectReadPreference0 = null;
let myObjectReadPreference1 = null;
let myObjectReadPreference2 = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject0') >= 0) {
myObjectReadPreference0 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject1') >= 0) {
myObjectReadPreference1 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject2') >= 0) {
myObjectReadPreference2 = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference0).toEqual(ReadPreference.PRIMARY);
expect(myObjectReadPreference1).toEqual(ReadPreference.PRIMARY);
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change includes read preference', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject1');
obj1.set('boolKey', true);
obj1.set('myObject0', obj0);
const obj2 = new Parse.Object('MyObject2');
obj2.set('boolKey', false);
obj2.set('myObject1', obj1);
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject2', (req) => {
req.readPreference = 'SECONDARY_PREFERRED';
req.includeReadPreference = 'SECONDARY';
});
const query = new Parse.Query('MyObject2');
query.equalTo('boolKey', false);
query.include('myObject1');
query.include('myObject1.myObject0');
query.find().then((results) => {
expect(results.length).toBe(1);
const firstResult = results[0];
expect(firstResult.get('boolKey')).toBe(false);
expect(firstResult.get('myObject1').get('boolKey')).toBe(true);
expect(firstResult.get('myObject1').get('myObject0').get('boolKey')).toBe(false);
let myObjectReadPreference0 = null;
let myObjectReadPreference1 = null;
let myObjectReadPreference2 = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject0') >= 0) {
myObjectReadPreference0 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject1') >= 0) {
myObjectReadPreference1 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject2') >= 0) {
myObjectReadPreference2 = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
done();
});
});
});
it('should find subqueries in primary by default', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject1');
obj1.set('boolKey', true);
obj1.set('myObject0', obj0);
const obj2 = new Parse.Object('MyObject2');
obj2.set('boolKey', false);
obj2.set('myObject1', obj1);
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject2', (req) => {
req.readPreference = 'SECONDARY';
});
const query0 = new Parse.Query('MyObject0');
query0.equalTo('boolKey', false);
const query1 = new Parse.Query('MyObject1');
query1.matchesQuery('myObject0', query0);
const query2 = new Parse.Query('MyObject2');
query2.matchesQuery('myObject1', query1);
query2.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference0 = null;
let myObjectReadPreference1 = null;
let myObjectReadPreference2 = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject0') >= 0) {
myObjectReadPreference0 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject1') >= 0) {
myObjectReadPreference1 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject2') >= 0) {
myObjectReadPreference2 = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference0).toEqual(ReadPreference.PRIMARY);
expect(myObjectReadPreference1).toEqual(ReadPreference.PRIMARY);
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY);
done();
});
});
});
it('should change subqueries read preference when using matchesQuery', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject1');
obj1.set('boolKey', true);
obj1.set('myObject0', obj0);
const obj2 = new Parse.Object('MyObject2');
obj2.set('boolKey', false);
obj2.set('myObject1', obj1);
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject2', (req) => {
req.readPreference = 'SECONDARY_PREFERRED';
req.subqueryReadPreference = 'SECONDARY';
});
const query0 = new Parse.Query('MyObject0');
query0.equalTo('boolKey', false);
const query1 = new Parse.Query('MyObject1');
query1.matchesQuery('myObject0', query0);
const query2 = new Parse.Query('MyObject2');
query2.matchesQuery('myObject1', query1);
query2.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference0 = null;
let myObjectReadPreference1 = null;
let myObjectReadPreference2 = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject0') >= 0) {
myObjectReadPreference0 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject1') >= 0) {
myObjectReadPreference1 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject2') >= 0) {
myObjectReadPreference2 = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
done();
});
});
});
it('should change subqueries read preference when using doesNotMatchQuery', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject1');
obj1.set('boolKey', true);
obj1.set('myObject0', obj0);
const obj2 = new Parse.Object('MyObject2');
obj2.set('boolKey', false);
obj2.set('myObject1', obj1);
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject2', (req) => {
req.readPreference = 'SECONDARY_PREFERRED';
req.subqueryReadPreference = 'SECONDARY';
});
const query0 = new Parse.Query('MyObject0');
query0.equalTo('boolKey', false);
const query1 = new Parse.Query('MyObject1');
query1.doesNotMatchQuery('myObject0', query0);
const query2 = new Parse.Query('MyObject2');
query2.doesNotMatchQuery('myObject1', query1);
query2.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference0 = null;
let myObjectReadPreference1 = null;
let myObjectReadPreference2 = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject0') >= 0) {
myObjectReadPreference0 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject1') >= 0) {
myObjectReadPreference1 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject2') >= 0) {
myObjectReadPreference2 = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
done();
});
});
});
it('should change subqueries read preference when using matchesKeyInQuery and doesNotMatchKeyInQuery', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
const obj1 = new Parse.Object('MyObject1');
obj1.set('boolKey', true);
obj1.set('myObject0', obj0);
const obj2 = new Parse.Object('MyObject2');
obj2.set('boolKey', false);
obj2.set('myObject1', obj1);
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
Parse.Cloud.beforeFind('MyObject2', (req) => {
req.readPreference = 'SECONDARY_PREFERRED';
req.subqueryReadPreference = 'SECONDARY';
});
const query0 = new Parse.Query('MyObject0');
query0.equalTo('boolKey', false);
const query1 = new Parse.Query('MyObject1');
query1.equalTo('boolKey', true);
const query2 = new Parse.Query('MyObject2');
query2.matchesKeyInQuery('boolKey', 'boolKey', query0);
query2.doesNotMatchKeyInQuery('boolKey', 'boolKey', query1);
query2.find().then((results) => {
expect(results.length).toBe(1);
expect(results[0].get('boolKey')).toBe(false);
let myObjectReadPreference0 = null;
let myObjectReadPreference1 = null;
let myObjectReadPreference2 = null;
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
if (call.args[0].indexOf('MyObject0') >= 0) {
myObjectReadPreference0 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject1') >= 0) {
myObjectReadPreference1 = call.args[2].readPreference.preference;
}
if (call.args[0].indexOf('MyObject2') >= 0) {
myObjectReadPreference2 = call.args[2].readPreference.preference;
}
});
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
done();
});
});
});
});
+56
View File
@@ -0,0 +1,56 @@
var RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
/*
To run this test part of the complete suite
set PARSE_SERVER_TEST_CACHE='redis'
and make sure a redis server is available on the default port
*/
describe_only(() => {
return process.env.PARSE_SERVER_TEST_CACHE === 'redis';
})('RedisCacheAdapter', function() {
var KEY = 'hello';
var VALUE = 'world';
function wait(sleep) {
return new Promise(function(resolve) {
setTimeout(resolve, sleep);
})
}
it('should get/set/clear', (done) => {
var cache = new RedisCacheAdapter({
ttl: NaN
});
cache.put(KEY, VALUE)
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(VALUE))
.then(() => cache.clear())
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(null))
.then(done);
});
it('should expire after ttl', (done) => {
var cache = new RedisCacheAdapter(null, 1);
cache.put(KEY, VALUE)
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(VALUE))
.then(wait.bind(null, 2))
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(null))
.then(done);
});
it('should find un-expired records', (done) => {
var cache = new RedisCacheAdapter(null, 5);
cache.put(KEY, VALUE)
.then(() => cache.get(KEY))
.then((value) => expect(value).toEqual(VALUE))
.then(wait.bind(null, 1))
.then(() => cache.get(KEY))
.then((value) => expect(value).not.toEqual(null))
.then(done);
});
});
+2 -2
View File
@@ -10,14 +10,14 @@ describe('RedisPubSub', function() {
});
it('can create publisher', function() {
var publisher = RedisPubSub.createPublisher({redisURL: 'redisAddress'});
RedisPubSub.createPublisher({redisURL: 'redisAddress'});
var redis = require('redis');
expect(redis.createClient).toHaveBeenCalledWith('redisAddress', { no_ready_check: true });
});
it('can create subscriber', function() {
var subscriber = RedisPubSub.createSubscriber({redisURL: 'redisAddress'});
RedisPubSub.createSubscriber({redisURL: 'redisAddress'});
var redis = require('redis');
expect(redis.createClient).toHaveBeenCalledWith('redisAddress', { no_ready_check: true });
+51 -53
View File
@@ -1,12 +1,10 @@
'use strict'
// These tests check the "find" functionality of the REST API.
var auth = require('../src/Auth');
var cache = require('../src/cache');
var Config = require('../src/Config');
var rest = require('../src/rest');
var querystring = require('querystring');
var request = require('request');
var rp = require('request-promise');
var config;
@@ -15,7 +13,7 @@ var nobody = auth.nobody(config);
describe('rest query', () => {
beforeEach(() => {
beforeEach(() => {
config = new Config('test');
database = config.database;
});
@@ -33,10 +31,10 @@ describe('rest query', () => {
rest.create(config, nobody, 'TestObject', {foo: 'baz'}
).then(() => {
return rest.create(config, nobody,
'TestObject', {foo: 'qux'});
'TestObject', {foo: 'qux'});
}).then(() => {
return rest.find(config, nobody,
'TestObject', {}, {limit: 1});
'TestObject', {}, {limit: 1});
}).then((response) => {
expect(response.results.length).toEqual(1);
expect(response.results[0].foo).toBeTruthy();
@@ -118,11 +116,11 @@ describe('rest query', () => {
activity.photo.objectId = photo.objectId;
activity.fromUser.objectId = user.objectId;
return rest.create(config, nobody,
'TestActivity', activity);
'TestActivity', activity);
}).then(() => {
queryWhere.photo.objectId = photo.objectId;
return rest.find(config, nobody,
'TestActivity', queryWhere, queryOptions);
'TestActivity', queryWhere, queryOptions);
}).then((response) => {
var results = response.results;
expect(results.length).toEqual(1);
@@ -145,62 +143,62 @@ describe('rest query', () => {
expect(err.message).toEqual('This user is not allowed to access ' +
'non-existent class: ClientClassCreation');
done();
});
});
});
it('query existent class when disabled client class creation', (done) => {
var customConfig = Object.assign({}, config, {allowClientClassCreation: false});
config.database.loadSchema()
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
.then(actualSchema => {
expect(actualSchema.className).toEqual('ClientClassCreation');
return rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {});
})
.then((result) => {
expect(result.results.length).toEqual(0);
done();
}, err => {
fail('Should not throw error')
});
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
.then(actualSchema => {
expect(actualSchema.className).toEqual('ClientClassCreation');
return rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {});
})
.then((result) => {
expect(result.results.length).toEqual(0);
done();
}, () => {
fail('Should not throw error')
});
});
it('query with wrongly encoded parameter', (done) => {
rest.create(config, nobody, 'TestParameterEncode', {foo: 'bar'}
).then(() => {
return rest.create(config, nobody,
'TestParameterEncode', {foo: 'baz'});
'TestParameterEncode', {foo: 'baz'});
}).then(() => {
var headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
let p0 = rp.get({
headers: headers,
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
+ querystring.stringify({
where: '{"foo":{"$ne": "baz"}}',
limit: 1
}).replace('=', '%3D'),
}).then(fail, (response) => {
let error = response.error;
var b = JSON.parse(error);
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
});
let p1 = rp.get({
const p0 = rp.get({
headers: headers,
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
+ querystring.stringify({
limit: 1
}).replace('=', '%3D'),
}).then(fail, (response) => {
let error = response.error;
var b = JSON.parse(error);
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
});
url: 'http://localhost:8378/1/classes/TestParameterEncode?' + querystring.stringify({
where: '{"foo":{"$ne": "baz"}}',
limit: 1
}).replace('=', '%3D'),
})
.then(fail, (response) => {
const error = response.error;
var b = JSON.parse(error);
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
});
const p1 = rp.get({
headers: headers,
url: 'http://localhost:8378/1/classes/TestParameterEncode?' + querystring.stringify({
limit: 1
}).replace('=', '%3D'),
})
.then(fail, (response) => {
const error = response.error;
var b = JSON.parse(error);
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
});
return Promise.all([p0, p1]);
}).then(done).catch((err) => {
}).then(done).catch((err) => {
jfail(err);
fail('should not fail');
done();
@@ -237,21 +235,21 @@ describe('rest query', () => {
});
it('makes sure null pointers are handed correctly #2189', done => {
let object = new Parse.Object('AnObject');
let anotherObject = new Parse.Object('AnotherObject');
anotherObject.save().then(() => {
const object = new Parse.Object('AnObject');
const anotherObject = new Parse.Object('AnotherObject');
anotherObject.save().then(() => {
object.set('values', [null, null, anotherObject]);
return object.save();
}).then(() => {
let query = new Parse.Query('AnObject');
}).then(() => {
const query = new Parse.Query('AnObject');
query.include('values');
return query.first();
}).then((result) => {
let values = result.get('values');
}).then((result) => {
const values = result.get('values');
expect(values.length).toBe(3);
let anotherObjectFound = false;
let nullCounts = 0;
for(let value of values) {
for(const value of values) {
if (value === null) {
nullCounts++;
} else if (value instanceof Parse.Object) {
@@ -261,7 +259,7 @@ describe('rest query', () => {
expect(nullCounts).toBe(2);
expect(anotherObjectFound).toBeTruthy();
done();
}, (err) => {
}, (err) => {
console.error(err);
fail(err);
done();
+25 -25
View File
@@ -16,97 +16,97 @@ function createUser() {
describe_only_db('mongo')('revocable sessions', () => {
beforeEach((done) => {
beforeEach((done) => {
// Create 1 user with the legacy
createUser().then(done);
});
it('should upgrade legacy session token', done => {
let user = Parse.Object.fromJSON({
it('should upgrade legacy session token', done => {
const user = Parse.Object.fromJSON({
className: '_User',
objectId: '1234567890',
sessionToken: sessionToken
});
user._upgradeToRevocableSession().then((res) => {
user._upgradeToRevocableSession().then((res) => {
expect(res.getSessionToken().indexOf('r:')).toBe(0);
const config = new Config(Parse.applicationId);
// use direct access to the DB to make sure we're not
// getting the session token stripped
return config.database.loadSchema().then(schemaController => {
return config.database.loadSchema().then(schemaController => {
return schemaController.getOneSchema('_User', true)
}).then((schema) => {
return config.database.adapter.find('_User', schema, {objectId: '1234567890'}, {})
}).then((results) => {
}).then((results) => {
expect(results.length).toBe(1);
expect(results[0].sessionToken).toBeUndefined();
});
}).then(() => {
}).then(() => {
done();
}, (err) => {
}, (err) => {
jfail(err);
done();
});
});
it('should be able to become with revocable session token', done => {
let user = Parse.Object.fromJSON({
it('should be able to become with revocable session token', done => {
const user = Parse.Object.fromJSON({
className: '_User',
objectId: '1234567890',
sessionToken: sessionToken
});
user._upgradeToRevocableSession().then((res) => {
user._upgradeToRevocableSession().then((res) => {
expect(res.getSessionToken().indexOf('r:')).toBe(0);
return Parse.User.logOut().then(() => {
return Parse.User.become(res.getSessionToken())
}).then((user) => {
return Parse.User.logOut().then(() => {
return Parse.User.become(res.getSessionToken())
}).then((user) => {
expect(user.id).toEqual('1234567890');
});
}).then(() => {
}).then(() => {
done();
}, (err) => {
}, (err) => {
jfail(err);
done();
});
});
it('should not upgrade bad legacy session token', done => {
it('should not upgrade bad legacy session token', done => {
rp.post({
url: Parse.serverURL+'/upgradeToRevocableSession',
url: Parse.serverURL + '/upgradeToRevocableSession',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Rest-API-Key': 'rest',
'X-Parse-Session-Token': 'badSessionToken'
},
json: true
}).then((res) => {
}).then(() => {
fail('should not be able to upgrade a bad token');
}, (response) => {
expect(response.statusCode).toBe(400);
expect(response.error).not.toBeUndefined();
expect(response.error.code).toBe(Parse.Error.INVALID_SESSION_TOKEN);
expect(response.error.error).toEqual('invalid legacy session token');
}).then(() => {
}).then(() => {
done();
});
});
it('should not crash without session token #2720', done => {
it('should not crash without session token #2720', done => {
rp.post({
url: Parse.serverURL+'/upgradeToRevocableSession',
url: Parse.serverURL + '/upgradeToRevocableSession',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Rest-API-Key': 'rest'
},
json: true
}).then((res) => {
}).then(() => {
fail('should not be able to upgrade a bad token');
}, (response) => {
expect(response.statusCode).toBe(404);
expect(response.error).not.toBeUndefined();
expect(response.error.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
expect(response.error.error).toEqual('invalid session');
}).then(() => {
}).then(() => {
done();
});
});
})
})
+950 -512
View File
File diff suppressed because it is too large Load Diff
+9 -11
View File
@@ -4,20 +4,21 @@ describe('SessionTokenCache', function() {
beforeEach(function(done) {
var Parse = require('parse/node');
// Mock parse
var mockUser = {
become: jasmine.createSpy('become').and.returnValue(Parse.Promise.as({
id: 'userId'
}))
}
jasmine.mockLibrary('parse/node', 'User', mockUser);
spyOn(Parse, "Query").and.returnValue({
first: jasmine.createSpy("first").and.returnValue(Parse.Promise.as(new Parse.Object("_Session", {
user: new Parse.User({id:"userId"})
}))),
equalTo: function(){}
})
done();
});
it('can get undefined userId', function(done) {
var sessionTokenCache = new SessionTokenCache();
sessionTokenCache.getUserId(undefined).then((userIdFromCache) => {
sessionTokenCache.getUserId(undefined).then(() => {
}, (error) => {
expect(error).not.toBeNull();
done();
@@ -46,7 +47,4 @@ describe('SessionTokenCache', function() {
});
});
afterEach(function() {
jasmine.restoreLibrary('parse/node', 'User');
});
});
+22 -8
View File
@@ -1,7 +1,7 @@
let twitter = require('../src/authDataManager/twitter');
const twitter = require('../src/Adapters/Auth/twitter');
describe('Twitter Auth', () => {
it('should use the proper configuration', () => {
describe('Twitter Auth', () => {
it('should use the proper configuration', () => {
// Multiple options, consumer_key found
expect(twitter.handleMultipleConfigurations({
consumer_key: 'hello',
@@ -9,10 +9,10 @@ describe('Twitter Auth', () => {
consumer_key: 'hello'
}, {
consumer_key: 'world'
}]).consumer_key).toEqual('hello')
}]).consumer_key).toEqual('hello');
// Multiple options, consumer_key not found
expect(function(){
expect(function(){
twitter.handleMultipleConfigurations({
consumer_key: 'some',
}, [{
@@ -23,7 +23,7 @@ describe('Twitter Auth', () => {
}).toThrow();
// Multiple options, consumer_key not found
expect(function(){
expect(function(){
twitter.handleMultipleConfigurations({
auth_token: 'token',
}, [{
@@ -47,4 +47,18 @@ describe('Twitter Auth', () => {
consumer_key: 'hello'
}).consumer_key).toEqual('hello');
});
});
it("Should fail with missing options", (done) => {
try {
twitter.validateAuthData({
consumer_key: 'key',
consumer_secret: 'secret',
auth_token: 'token',
auth_token_secret: 'secret'
}, undefined);
} catch (error) {
jequal(error.message, 'Twitter auth configuration missing');
done();
}
});
});
+75 -76
View File
@@ -1,103 +1,102 @@
'use strict';
var request = require('request');
const Parse = require("parse/node");
let Config = require('../src/Config');
const Config = require('../src/Config');
describe('Uniqueness', function() {
it('fail when create duplicate value in unique field', done => {
let obj = new Parse.Object('UniqueField');
const obj = new Parse.Object('UniqueField');
obj.set('unique', 'value');
obj.save().then(() => {
expect(obj.id).not.toBeUndefined();
let config = new Config('test');
const config = new Config('test');
return config.database.adapter.ensureUniqueness('UniqueField', { fields: { unique: { __type: 'String' } } }, ['unique'])
})
.then(() => {
let obj = new Parse.Object('UniqueField');
obj.set('unique', 'value');
return obj.save()
}).then(() => {
fail('Saving duplicate field should have failed');
done();
}, error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
.then(() => {
const obj = new Parse.Object('UniqueField');
obj.set('unique', 'value');
return obj.save()
}).then(() => {
fail('Saving duplicate field should have failed');
done();
}, error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
});
it('unique indexing works on pointer fields', done => {
let obj = new Parse.Object('UniquePointer');
const obj = new Parse.Object('UniquePointer');
obj.save({ string: 'who cares' })
.then(() => obj.save({ ptr: obj }))
.then(() => {
let config = new Config('test');
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
string: { __type: 'String' },
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
} }, ['ptr']);
})
.then(() => {
let newObj = new Parse.Object('UniquePointer')
newObj.set('ptr', obj)
return newObj.save()
})
.then(() => {
fail('save should have failed due to duplicate value');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
.then(() => obj.save({ ptr: obj }))
.then(() => {
const config = new Config('test');
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
string: { __type: 'String' },
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
} }, ['ptr']);
})
.then(() => {
const newObj = new Parse.Object('UniquePointer')
newObj.set('ptr', obj)
return newObj.save()
})
.then(() => {
fail('save should have failed due to duplicate value');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
});
it('fails when attempting to ensure uniqueness of fields that are not currently unique', done => {
let o1 = new Parse.Object('UniqueFail');
const o1 = new Parse.Object('UniqueFail');
o1.set('key', 'val');
let o2 = new Parse.Object('UniqueFail');
const o2 = new Parse.Object('UniqueFail');
o2.set('key', 'val');
Parse.Object.saveAll([o1, o2])
.then(() => {
let config = new Config('test');
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
.then(() => {
const config = new Config('test');
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
});
it_exclude_dbs(['postgres'])('can do compound uniqueness', done => {
let config = new Config('test');
const config = new Config('test');
config.database.adapter.ensureUniqueness('CompoundUnique', { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } }, ['k1', 'k2'])
.then(() => {
let o1 = new Parse.Object('CompoundUnique');
o1.set('k1', 'v1');
o1.set('k2', 'v2');
return o1.save();
})
.then(() => {
let o2 = new Parse.Object('CompoundUnique');
o2.set('k1', 'v1');
o2.set('k2', 'not a dupe');
return o2.save();
})
.then(() => {
let o3 = new Parse.Object('CompoundUnique');
o3.set('k1', 'not a dupe');
o3.set('k2', 'v2');
return o3.save();
})
.then(() => {
let o4 = new Parse.Object('CompoundUnique');
o4.set('k1', 'v1');
o4.set('k2', 'v2');
return o4.save();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
.then(() => {
const o1 = new Parse.Object('CompoundUnique');
o1.set('k1', 'v1');
o1.set('k2', 'v2');
return o1.save();
})
.then(() => {
const o2 = new Parse.Object('CompoundUnique');
o2.set('k1', 'v1');
o2.set('k2', 'not a dupe');
return o2.save();
})
.then(() => {
const o3 = new Parse.Object('CompoundUnique');
o3.set('k1', 'not a dupe');
o3.set('k2', 'v2');
return o3.save();
})
.then(() => {
const o4 = new Parse.Object('CompoundUnique');
o4.set('k1', 'v1');
o4.set('k2', 'v2');
return o4.save();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
});
});
+59
View File
@@ -0,0 +1,59 @@
var UserController = require('../src/Controllers/UserController').UserController;
var emailAdapter = require('./MockEmailAdapter')
var AppCache = require('../src/cache').AppCache;
describe('UserController', () => {
var user = {
_email_verify_token: 'testToken',
username: 'testUser',
email: 'test@example.com'
}
describe('sendVerificationEmail', () => {
describe('parseFrameURL not provided', () => {
it('uses publicServerURL', (done) => {
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
publicServerURL: 'http://www.example.com',
customPages: {
parseFrameURL: undefined
}
}))
emailAdapter.sendVerificationEmail = (options) => {
expect(options.link).toEqual('http://www.example.com/apps/test/verify_email?token=testToken&username=testUser')
done()
}
var userController = new UserController(emailAdapter, 'test', {
verifyUserEmails: true
})
userController.sendVerificationEmail(user)
})
})
describe('parseFrameURL provided', () => {
it('uses parseFrameURL and includes the destination in the link parameter', (done) => {
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
publicServerURL: 'http://www.example.com',
customPages: {
parseFrameURL: 'http://someother.example.com/handle-parse-iframe'
}
}))
emailAdapter.sendVerificationEmail = (options) => {
expect(options.link).toEqual('http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=testToken&username=testUser')
done()
}
var userController = new UserController(emailAdapter, 'test', {
verifyUserEmails: true
})
userController.sendVerificationEmail(user)
})
})
})
});
+503
View File
@@ -0,0 +1,503 @@
'use strict';
const Parse = require('parse/node');
const request = require('request-promise');
// const Config = require('../src/Config');
const EMAIL = 'foo@bar.com';
const ZIP = '10001';
const SSN = '999-99-9999';
describe('Personally Identifiable Information', () => {
let user;
beforeEach(done => {
return Parse.User.signUp('tester', 'abc')
.then(loggedInUser => user = loggedInUser)
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
.then(() => user
.set('email', EMAIL)
.set('zip', ZIP)
.set('ssn', SSN)
.save())
.then(() => done());
});
it('should be able to get own PII via API with object', (done) => {
const userObj = new (Parse.Object.extend(Parse.User));
userObj.id = user.id;
userObj.fetch().then(
fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
}, e => console.error('error', e))
.done(() => done());
});
it('should not be able to get PII via API with object', (done) => {
Parse.User.logOut()
.then(() => {
const userObj = new (Parse.Object.extend(Parse.User));
userObj.id = user.id;
userObj.fetch().then(
fetchedUser => {
expect(fetchedUser.get('email')).toBe(undefined);
})
.fail(e => {
done.fail(JSON.stringify(e));
})
.done(() => done());
});
});
it('should be able to get PII via API with object using master key', (done) => {
Parse.User.logOut()
.then(() => {
const userObj = new (Parse.Object.extend(Parse.User));
userObj.id = user.id;
userObj.fetch({ useMasterKey: true }).then(
fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
}, e => console.error('error', e))
.done(() => done());
});
});
it('should be able to get own PII via API with Find', (done) => {
new Parse.Query(Parse.User)
.first()
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
});
});
it('should not get PII via API with Find', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.first()
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(undefined);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
})
);
});
it('should get PII via API with Find using master key', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.first({ useMasterKey: true })
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
})
);
});
it('should be able to get own PII via API with Get', (done) => {
new Parse.Query(Parse.User)
.get(user.id)
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
});
});
it('should not get PII via API with Get', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.get(user.id)
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(undefined);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
})
);
});
it('should get PII via API with Get using master key', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.get(user.id, { useMasterKey: true })
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
})
);
});
it('should not get PII via REST', (done) => {
request.get({
url: 'http://localhost:8378/1/classes/_User',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test'
}
})
.then(
result => {
const fetchedUser = result.results[0];
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(undefined);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST with self credentials', (done) => {
request.get({
url: 'http://localhost:8378/1/classes/_User',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
'X-Parse-Session-Token': user.getSessionToken()
}
})
.then(
result => {
const fetchedUser = result.results[0];
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST using master key', (done) => {
request.get({
url: 'http://localhost:8378/1/classes/_User',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
}
})
.then(
result => {
const fetchedUser = result.results[0];
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should not get PII via REST by ID', (done) => {
request.get({
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test'
}
})
.then(
result => {
const fetchedUser = result;
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(undefined);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST by ID with self credentials', (done) => {
request.get({
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
'X-Parse-Session-Token': user.getSessionToken()
}
})
.then(
result => {
const fetchedUser = result;
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST by ID with master key', (done) => {
request.get({
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
'X-Parse-Master-Key': 'test',
}
})
.then(
result => {
const fetchedUser = result;
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
},
e => console.error('error', e.message)
).done(() => done());
});
describe('with configured sensitive fields', () => {
beforeEach((done) => {
reconfigureServer({ userSensitiveFields: ['ssn', 'zip'] })
.then(() => done());
});
it('should be able to get own PII via API with object', (done) => {
const userObj = new (Parse.Object.extend(Parse.User));
userObj.id = user.id;
userObj.fetch().then(
fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
}, e => done.fail(e));
});
it('should not be able to get PII via API with object', (done) => {
Parse.User.logOut()
.then(() => {
const userObj = new (Parse.Object.extend(Parse.User));
userObj.id = user.id;
userObj.fetch().then(
fetchedUser => {
expect(fetchedUser.get('email')).toBe(undefined);
expect(fetchedUser.get('zip')).toBe(undefined);
expect(fetchedUser.get('ssn')).toBe(undefined);
}, e => console.error('error', e))
.done(() => done());
});
});
it('should be able to get PII via API with object using master key', (done) => {
Parse.User.logOut()
.then(() => {
const userObj = new (Parse.Object.extend(Parse.User));
userObj.id = user.id;
userObj.fetch({ useMasterKey: true }).then(
fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
}, e => console.error('error', e))
.done(() => done());
});
});
it('should be able to get own PII via API with Find', (done) => {
new Parse.Query(Parse.User)
.first()
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
});
});
it('should not get PII via API with Find', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.first()
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(undefined);
expect(fetchedUser.get('zip')).toBe(undefined);
expect(fetchedUser.get('ssn')).toBe(undefined);
done();
})
);
});
it('should get PII via API with Find using master key', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.first({ useMasterKey: true })
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
})
);
});
it('should be able to get own PII via API with Get', (done) => {
new Parse.Query(Parse.User)
.get(user.id)
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
});
});
it('should not get PII via API with Get', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.get(user.id)
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(undefined);
expect(fetchedUser.get('zip')).toBe(undefined);
expect(fetchedUser.get('ssn')).toBe(undefined);
done();
})
);
});
it('should get PII via API with Get using master key', (done) => {
Parse.User.logOut()
.then(() => new Parse.Query(Parse.User)
.get(user.id, { useMasterKey: true })
.then(fetchedUser => {
expect(fetchedUser.get('email')).toBe(EMAIL);
expect(fetchedUser.get('zip')).toBe(ZIP);
expect(fetchedUser.get('ssn')).toBe(SSN);
done();
})
);
});
it('should not get PII via REST', (done) => {
request.get({
url: 'http://localhost:8378/1/classes/_User',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test'
}
})
.then(
result => {
const fetchedUser = result.results[0];
expect(fetchedUser.zip).toBe(undefined);
expect(fetchedUser.ssn).toBe(undefined);
expect(fetchedUser.email).toBe(undefined);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST with self credentials', (done) => {
request.get({
url: 'http://localhost:8378/1/classes/_User',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
'X-Parse-Session-Token': user.getSessionToken()
}
})
.then(
result => {
const fetchedUser = result.results[0];
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
expect(fetchedUser.ssn).toBe(SSN);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST using master key', (done) => {
request.get({
url: 'http://localhost:8378/1/classes/_User',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
}
})
.then(
result => {
const fetchedUser = result.results[0];
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
expect(fetchedUser.ssn).toBe(SSN);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should not get PII via REST by ID', (done) => {
request.get({
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test'
}
})
.then(
result => {
const fetchedUser = result;
expect(fetchedUser.zip).toBe(undefined);
expect(fetchedUser.email).toBe(undefined);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST by ID with self credentials', (done) => {
request.get({
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
'X-Parse-Session-Token': user.getSessionToken()
}
})
.then(
result => {
const fetchedUser = result;
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
},
e => console.error('error', e.message)
).done(() => done());
});
it('should get PII via REST by ID with master key', (done) => {
request.get({
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test',
'X-Parse-Master-Key': 'test',
}
})
.then(
result => {
const fetchedUser = result;
expect(fetchedUser.zip).toBe(ZIP);
expect(fetchedUser.email).toBe(EMAIL);
},
e => console.error('error', e.message)
).done(() => done());
});
});
});
File diff suppressed because it is too large Load Diff
+50 -51
View File
@@ -1,37 +1,36 @@
'use strict';
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
var Parse = require('parse/node').Parse;
var request = require('request');
describe('info logs', () => {
it("Verify INFO logs", (done) => {
var winstonLoggerAdapter = new WinstonLoggerAdapter();
winstonLoggerAdapter.log('info', 'testing info logs', () => {
var winstonLoggerAdapter = new WinstonLoggerAdapter();
winstonLoggerAdapter.log('info', 'testing info logs', () => {
winstonLoggerAdapter.query({
from: new Date(Date.now() - 500),
size: 100,
level: 'info'
}, (results) => {
if (results.length == 0) {
fail('The adapter should return non-empty results');
} else {
expect(results[0].message).toEqual('testing info logs');
}
// Check the error log
// Regression #2639
winstonLoggerAdapter.query({
from: new Date(Date.now() - 500),
size: 100,
level: 'info'
level: 'error'
}, (results) => {
if (results.length == 0) {
fail('The adapter should return non-empty results');
} else {
expect(results[0].message).toEqual('testing info logs');
}
// Check the error log
// Regression #2639
winstonLoggerAdapter.query({
from: new Date(Date.now() - 500),
size: 100,
level: 'error'
}, (results) => {
expect(results.length).toEqual(0);
done();
});
expect(results.length).toEqual(0);
done();
});
});
});
});
});
describe('error logs', () => {
@@ -59,42 +58,42 @@ describe('error logs', () => {
describe('verbose logs', () => {
it("mask sensitive information in _User class", (done) => {
reconfigureServer({ verbose: true })
.then(() => createTestUser())
.then(() => {
let winstonLoggerAdapter = new WinstonLoggerAdapter();
return winstonLoggerAdapter.query({
from: new Date(Date.now() - 500),
size: 100,
level: 'verbose'
});
}).then((results) => {
let logString = JSON.stringify(results);
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
expect(logString.match(/moon-y/g)).toBe(null);
var headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.get({
headers: headers,
url: 'http://localhost:8378/1/login?username=test&password=moon-y'
}, (error, response, body) => {
let winstonLoggerAdapter = new WinstonLoggerAdapter();
.then(() => createTestUser())
.then(() => {
const winstonLoggerAdapter = new WinstonLoggerAdapter();
return winstonLoggerAdapter.query({
from: new Date(Date.now() - 500),
size: 100,
level: 'verbose'
}).then((results) => {
let logString = JSON.stringify(results);
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
expect(logString.match(/moon-y/g)).toBe(null);
done();
});
});
}).catch((err) => {
fail(JSON.stringify(err));
done();
})
}).then((results) => {
const logString = JSON.stringify(results);
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
expect(logString.match(/moon-y/g)).toBe(null);
var headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.get({
headers: headers,
url: 'http://localhost:8378/1/login?username=test&password=moon-y'
}, () => {
const winstonLoggerAdapter = new WinstonLoggerAdapter();
return winstonLoggerAdapter.query({
from: new Date(Date.now() - 500),
size: 100,
level: 'verbose'
}).then((results) => {
const logString = JSON.stringify(results);
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
expect(logString.match(/moon-y/g)).toBe(null);
done();
});
});
}).catch((err) => {
fail(JSON.stringify(err));
done();
})
});
});

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