Compare commits

..
1 Commits
Author SHA1 Message Date
Florent Vilmart 9fc6e0a938 Fixes CI dist 2016-12-07 21:20:41 -05:00
238 changed files with 10524 additions and 40239 deletions
+3 -7
View File
@@ -1,13 +1,9 @@
{
"plugins": [
"transform-flow-strip-types",
"transform-object-rest-spread"
"transform-flow-strip-types"
],
"presets": [
["env", {
"targets": {
"node": "8"
}
}]
"es2015",
"stage-0"
]
}
+2 -3
View File
@@ -1,3 +1,2 @@
lib
coverage
lib/*
coverage/*
+1 -3
View File
@@ -20,8 +20,6 @@
"eol-last": 2,
"space-in-parens": ["error", "never"],
"no-multiple-empty-lines": 1,
"prefer-const": "error",
"space-infix-ops": "error",
"no-useless-escape": "off"
"prefer-const": "error"
}
}
-1
View File
@@ -7,4 +7,3 @@
[libs]
[options]
suppress_comment= \\(.\\|\n\\)*\\@flow-disable-next
-15
View File
@@ -1,15 +0,0 @@
* text=auto eol=lf
*.js text
*.html text
*.less text
*.json text
*.css text
*.xml text
*.md text
*.txt text
*.yml text
*.sql text
*.sh text
*.png binary
+32 -10
View File
@@ -1,30 +1,50 @@
We use GitHub Issues for bugs.
Please read the following instructions carefully.
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
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 vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
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/parse-community/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
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
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.
--
### 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
What you expected to happen.
[FILL THIS OUT]
#### Actual Outcome
What is happening instead.
[FILL THIS OUT]
### Environment Setup
@@ -42,4 +62,6 @@ What is happening instead.
### Logs/Trace
Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment.
You can turn on additional logging by configuring VERBOSE=1 in your environment.
[FILL THIS OUT]
-51
View File
@@ -1,51 +0,0 @@
---
name: Report an issue
about: Report an issue on parse-server
---
We use GitHub Issues for bugs.
If you have a non-bug question, ask on Stack Overflow or Server Fault:
- https://stackoverflow.com/questions/tagged/parse.com
- https://serverfault.com/tags/parse
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
### Issue Description
Describe your issue in as much detail as possible.
### Steps to reproduce
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
#### Expected Results
What you expected to happen.
#### Actual Outcome
What is happening instead.
### Environment Setup
- **Server**
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
- Operating System: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
- **Database**
- MongoDB version: [FILL THIS OUT]
- Storage engine: [FILL THIS OUT]
- Hardware: [FILL THIS OUT]
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
### Logs/Trace
Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment.
-1
View File
@@ -13,7 +13,6 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
+2
View File
@@ -0,0 +1,2 @@
instrumentation:
excludes: ["**/spec/**"]
-1
View File
@@ -1 +0,0 @@
8.10
-11
View File
@@ -1,11 +0,0 @@
{
"reporter": [
"lcov",
"text-summary"
],
"exclude": [
"**/spec/**",
"lib/"
]
}
+52 -63
View File
@@ -1,82 +1,71 @@
language: node_js
dist: trusty
node_js:
- '4.5'
- '6.1'
services:
- mongodb
- postgresql
- redis-server
- docker
addons:
postgresql: '9.5'
apt:
packages:
- postgresql-9.5-postgis-2.3
apt_packages:
- postgresql-9.5-postgis-2.3
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
branches:
only:
- master
- /^[0-9]+.[0-9]+.[0-9]+(-.*)?$/
- 3.x
- /^greenkeeper/.*$/
cache:
directories:
- node_modules
- "$HOME/.mongodb/versions"
# Test stage
stage: test
env:
global:
- COVERAGE_OPTION='./node_modules/.bin/nyc'
- NODE_VERSION=8.11.2
matrix:
- MONGODB_VERSION=3.2.13
- MONGODB_VERSION=3.4.4
- PARSE_SERVER_TEST_DB=postgres
- PARSE_SERVER_TEST_CACHE=redis
- NODE_VERSION=stable
matrix:
allow_failures:
- env: NODE_VERSION=stable
before_install:
- nvm install $NODE_VERSION
- nvm use $NODE_VERSION
- npm install -g greenkeeper-lockfile@1
before_script:
- node -e 'require("./lib/index.js")'
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
- silent=1 mongodb-runner --start
- greenkeeper-lockfile-update
script:
- npm run coverage
after_script:
- greenkeeper-lockfile-upload
- 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)
jobs:
include:
# release on github latest branch
- stage: release
node_js: '8.10'
env:
before_script: skip
after_script: skip
script: skip
deploy:
- provider: script
skip_cleanup: true
script: ./resources/npm-git.sh && npm install -g https://github.com/parse-community/parse-server#latest
on:
branch:
- master
- 3.x
- provider: npm
skip_cleanup: true
email:
secure: "YweTGc22uqFWpzbfiUa5ptBLRAy6tt6d9TZLwEkPtmnsWzN9dguGyKWmXiw0qL+848FWQ5PWzUgBn5XdigV9tF3rJY6RGs8i38WulNjwSoGuRZa3AChsQHAb1KenANcJybzhnwgEj9gRsrGZPEsyI2whfake/xLDtG91kHjpJANsd4gseOh6fdS4FIYCbyXvSbC7S0yZzIJkgSkMqJO7RJ8r0HYQ7srYIw31dM3ZXSmUYu+GaMDUUu3RLAGYoKaROxhDRnzkjdeLLiwZH8hQ/6CaqwqX54iJ6OS+MOQU3fi5ZXalA7RZvtC4RmrcCkaTf3i0f+5xejYIFgzXdYGMVm8DUc82tDw1s4b6Pb19bgi1xXOQ0IKzRmZuGxnvkRN61dnYdnpnnNuG97HXgVjiOigZXVLZkWazUdnf9zXqmkC+KxPfa4Ldqg0TMjQ9J14n6TXxRti8Tt0xMa1Uzho7VdsxdJy35Bghy398O6X8VdX6parfzEkX7c/JzcA3TIMJ9+S1dy3J4Tb2URB5367W6h7cDeXtGmwLTFu75Q1CNqRJkUGbSrX2NyMqG5tc8oaTM+OWvLxcbKPRy9T6qN7x2JmCHHaapf8/8VR9wCy2PwE5j+KWhmctEHqqOgrtG5gsjC0eCPJsposxZWyM7M6aUpXe3w+olBfKq9apUGStUSU="
api_key:
secure: "QprkaqQ+WCvZQR4qIEs5iS6peMCbRd8Hgt0s6HfdmhavNXwDFY8Bkdf6zJwWHLiqs4pyClXDZ2f6QiOs7y9IvJZ+wOIbsf4N5V6s06zOxJ0NAOwhe0mgWS3Us0zgXIfmW4BpmGnU4ql/qGL+9vNfyQJ7wxEJxVK7hiYh9Epu49E2jmefDqTX+SNSrDCg4HkRcxerxYGnAJDCP50QaNlyLSciODD6wHaddrSYkdvmISLMnLHug61OkE4OBIOWXfYV+e31kDj+zgczAfVQgekDKtaimCQclHFrmaEPl0KIm6wsDQAw5HWkepA/WZfv4SbCrDaKJDZw+LBI7dR0ezmiOH/zmWWrRW7D4wjkDGiumWjA8etuf8I4GRyC/d1RS+hnlvPr0Bu+WljuVxLoK3nhZOdiK5t4QlVDoGankkRjLylwFQgo8tzu5N0dc26z3ClowTwcKsjStmFr53gjCD7l3qoFjyPot1JlW3LPhG9Nch7rK33/7ONqVai3zxb1xB9ynd8TSzKi4/66LeYEDcGVM5A9Vmkp+egCnOhkOAXdI8O4jid98NANir+U4xmUYZ2PAMNmSeHlpSpwH2pC1/BHpDKA9RZEuEdr3sgmtuXXwwOCp+xvuVwxZZ6+gVEjG4nGTqSNxUIq1fzjGih8ElJtbM1Uhh2dVE9uxW7EC/oCuuk="
on:
tags: true
all_branches: true
repo: parse-community/parse-server
deploy:
- provider: npm
skip_cleanup: true
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: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
repo: ParsePlatform/parse-server
- provider: script
skip_cleanup: true
script: ./resources/npm-git.sh
on:
branch: master
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
- provider: script
skip_cleanup: true
script: ./resources/docker-hub.sh
on:
tags: true
all_branches: true
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
+4 -640
View File
@@ -1,641 +1,5 @@
## Parse Server Changelog
### master
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.2...master)
### 2.8.2
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.2...2.8.1)
##### Bug Fixes:
* Ensure legacy users without ACL's are not locked out, thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Improvements:
* Use common HTTP agent to increase webhooks performance, thanks to [Tyler Brock](https://github.com/TylerBrock)
* Adds withinPolygon support for Polygon objects, thanks to [Mads Bjerre](https://github.com/madsb)
#### Dependency Updates:
* [ws@5.2.0](https://www.npmjs.com/package/ws)
* [commander@2.15.1](https://www.npmjs.com/package/commander)
* [nodemon@1.17.5](https://www.npmjs.com/package/nodemon)
##### Devevelopment Dependencies Updates:
* [flow-bin@0.73.0](https://www.npmjs.com/package/flow-bin)
* [cross-env@5.1.6](https://www.npmjs.com/package/cross-env)
* [gaze@1.1.3](https://www.npmjs.com/package/gaze)
* [deepcopy@1.0.0](https://www.npmjs.com/package/deepcopy)
* [deep-diff@1.0.1](https://www.npmjs.com/package/deep-diff)
### 2.8.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.1...2.8.0)
Ensure all the files are properly exported to the final package.
### 2.8.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.0...2.7.4)
#### New Features
* Adding Mongodb element to add `arrayMatches` the #4762 (#4766), thanks to [Jérémy Piednoel](https://github.com/jeremypiednoel)
* Adds ability to Lockout users (#4749), thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Bug fixes:
* Fixes issue when using afterFind with relations (#4752), thanks to [Florent Vilmart](https://github.com/flovilmart)
* New query condition support to match all strings that starts with some other given strings (#3864), thanks to [Eduard Bosch Bertran](https://github.com/eduardbosch)
* Allow creation of indices on default fields (#4738), thanks to [Claire Neveu](https://github.com/ClaireNeveu)
* Purging empty class (#4676), thanks to [Diamond Lewis](https://github.com/dplewis)
* Postgres: Fixes issues comparing to zero or false (#4667), thanks to [Diamond Lewis](https://github.com/dplewis)
* Fix Aggregate Match Pointer (#4643), thanks to [Diamond Lewis](https://github.com/dplewis)
#### Improvements:
* Allow Parse.Error when returning from Cloud Code (#4695), thanks to [Saulo Tauil](https://github.com/saulogt)
* Fix typo: "requrest" -> "request" (#4761), thanks to [Joseph Frazier](https://github.com/josephfrazier)
* Send version for Vkontakte API (#4725), thanks to [oleg](https://github.com/alekoleg)
* Ensure we respond with invalid password even if email is unverified (#4708), thanks to [dblythy](https://github.com/dblythy)
* Add _password_history to default sensitive data (#4699), thanks to [Jong Eun Lee](https://github.com/yomybaby)
* Check for node version in postinstall script (#4657), thanks to [Diamond Lewis](https://github.com/dplewis)
* Remove FB Graph API version from URL to use the oldest non deprecated version, thanks to [SebC](https://github.com/SebC99)
#### Dependency Updates:
* [@parse/push-adapter@2.0.3](https://www.npmjs.com/package/@parse/push-adapter)
* [@parse/simple-mailgun-adapter@1.0.2](https://www.npmjs.com/package/@parse/simple-mailgun-adapter)
* [uws@10.148.0](https://www.npmjs.com/package/uws)
* [body-parser@1.18.3](https://www.npmjs.com/package/body-parser)
* [mime@2.3.1](https://www.npmjs.com/package/mime)
* [request@2.85.0](https://www.npmjs.com/package/request)
* [mongodb@3.0.7](https://www.npmjs.com/package/mongodb)
* [bcrypt@2.0.1](https://www.npmjs.com/package/bcrypt)
* [ws@5.1.1](https://www.npmjs.com/package/ws)
##### Devevelopment Dependencies Updates:
* [cross-env@5.1.5](https://www.npmjs.com/package/cross-env)
* [flow-bin@0.71.0](https://www.npmjs.com/package/flow-bin)
* [deep-diff@1.0.0](https://www.npmjs.com/package/deep-diff)
* [nodemon@1.17.3](https://www.npmjs.com/package/nodemon)
### 2.7.4
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.4...2.7.3)
#### Bug Fixes:
* Fixes an issue affecting polygon queries, thanks to [Diamond Lewis](https://github.com/dplewis)
#### Dependency Updates:
* [pg-promise@8.2.1](https://www.npmjs.com/package/pg-promise)
##### Development Dependencies Updates:
* [nodemon@1.17.1](https://www.npmjs.com/package/nodemon)
### 2.7.3
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.3...2.7.2)
#### Improvements:
* Improve documentation for LiveQuery options, thanks to [Arthur Cinader](https://github.com/acinader)
* Improve documentation for using cloud code with docker, thanks to [Stephen Tuso](https://github.com/stephentuso)
* Adds support for Facebook's AccountKit, thanks to [6thfdwp](https://github.com/6thfdwp)
* Disable afterFind routines when running aggregates, thanks to [Diamond Lewis](https://github.com/dplewis)
* Improve support for distinct aggregations of nulls, thanks to [Diamond Lewis](https://github.com/dplewis)
* Regenreate the email verification token when requesting a new email, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
#### Bug Fixes:
* Fix issue affecting readOnly masterKey and purge command, thanks to [AreyouHappy](https://github.com/AreyouHappy)
* Fixes Issue unsetting in beforeSave doesn't allow object creation, thanks to [Diamond Lewis](https://github.com/dplewis)
* Fixes issue crashing server on invalid live query payload, thanks to [fridays](https://github.com/fridays)
* Fixes issue affecting postgres storage adapter "undefined property '__op'", thanks to [Tyson Andre](https://github,com/TysonAndre)
#### Dependency Updates:
* [winston@2.4.1](https://www.npmjs.com/package/winston)
* [pg-promise@8.2.0](https://www.npmjs.com/package/pg-promise)
* [commander@2.15.0](https://www.npmjs.com/package/commander)
* [lru-cache@4.1.2](https://www.npmjs.com/package/lru-cache)
* [parse@1.11.1](https://www.npmjs.com/package/parse)
* [ws@5.0.0](https://www.npmjs.com/package/ws)
* [mongodb@3.0.4](https://www.npmjs.com/package/mongodb)
* [lodash@4.17.5](https://www.npmjs.com/package/lodash)
##### Devevelopment Dependencies Updates:
* [cross-env@5.1.4](https://www.npmjs.com/package/cross-env)
* [flow-bin@0.67.1](https://www.npmjs.com/package/flow-bin)
* [jasmine@3.1.0](https://www.npmjs.com/package/jasmine)
* [parse@1.11.1](https://www.npmjs.com/package/parse)
* [babel-eslint@8.2.2](https://www.npmjs.com/package/babel-eslint)
* [nodemon@1.15.0](https://www.npmjs.com/package/nodemon)
### 2.7.2
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.2...2.7.1)
#### Improvements:
* Improved match aggregate
* Do not mark the empty pushs as failed
* Support pointer in aggregate query
* Introduces flow types for storage
* Postgres: Refactoring of Postgres Storage Adapter
* Postgres: Support for multiple projection in aggregate
* Postgres: performance optimizations
* Adds infos about vulnerability disclosures
* Adds ability to login with email when provided as username
#### Bug Fixes
* Scrub Passwords with URL Encoded Characters
* Fixes issue affecting using sorting in beforeFind
#### Dependency Updates:
* [commander@2.13.0](https://www.npmjs.com/package/commander)
* [semver@5.5.0](https://www.npmjs.com/package/semver)
* [pg-promise@7.4.0](https://www.npmjs.com/package/pg-promise)
* [ws@4.0.0](https://www.npmjs.com/package/ws)
* [mime@2.2.0](https://www.npmjs.com/package/mime)
* [parse@1.11.0](https://www.npmjs.com/package/parse)
##### Devevelopment Dependencies Updates:
* [nodemon@1.14.11](https://www.npmjs.com/package/nodemon)
* [flow-bin@0.64.0](https://www.npmjs.com/package/flow-bin)
* [jasmine@2.9.0](https://www.npmjs.com/package/jasmine)
* [cross-env@5.1.3](https://www.npmjs.com/package/cross-env)
### 2.7.1
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.1...2.7.0)
:warning: Fixes a security issue affecting Class Level Permissions
* Adds support for dot notation when using matchesKeyInQuery, thanks to [Henrik](https://github.com/bohemima) and [Arthur Cinader](https://github.com/acinader)
### 2.7.0
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.0...2.6.5)
:warning: This version contains an issue affecting Class Level Permissions on mongoDB. Please upgrade to 2.7.1.
Starting parse-server 2.7.0, the minimun nodejs version is 6.11.4, please update your engines before updating parse-server
#### New Features:
* Aggregation endpoints, thanks to [Diamond Lewis](https://github.com/dplewis)
* Adds indexation options onto Schema endpoints, thanks to [Diamond Lewis](https://github.com/dplewis)
#### Bug fixes:
* Fixes sessionTokens being overridden in 'find' (#4332), thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Proper `handleShutdown()` feature to close database connections (#4361), thanks to [CHANG, TZU-YEN](https://github.com/trylovetom)
* Fixes issue affecting state of _PushStatus objects, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Fixes issue affecting calling password reset password pages with wrong appid, thanks to [Bryan de Leon](https://github.com/bryandel)
* Fixes issue affecting duplicates _Sessions on successive logins, thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Improvements:
* Updates contributing guides, and improves windows support, thanks to [Addison Elliott](https://github.com/addisonelliott)
* Uses new official scoped packaged, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Improves health checks responses, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Add password confirmation to choose_password, thanks to [Worathiti Manosroi](https://github.com/pungme)
* Improve performance of relation queries, thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Dependency Updates:
* [commander@2.12.1](https://www.npmjs.com/package/commander)
* [ws@3.3.2](https://www.npmjs.com/package/ws)
* [uws@9.14.0](https://www.npmjs.com/package/uws)
* [pg-promise@7.3.2](https://www.npmjs.com/package/pg-promise)
* [parse@1.10.2](https://www.npmjs.com/package/parse)
* [pg-promise@7.3.1](https://www.npmjs.com/package/pg-promise)
##### Devevelopment Dependencies Updates:
* [cross-env@5.1.1](https://www.npmjs.com/package/cross-env)
### 2.6.5
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.5...2.6.4)
#### New Features:
* Adds support for read-only masterKey, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Adds support for relative time queries (mongodb only), thanks to [Marvel Mathew](https://github.com/marvelm)
#### Improvements:
* Handle possible afterSave exception, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Add support for expiration interval in Push, thanks to [Marvel Mathew](https://github.com/marvelm)
#### Bug Fixes:
* The REST API key was improperly inferred from environment when using the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
### 2.6.4
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.4...2.6.3)
#### Improvements:
* Improves management of configurations and default values, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Adds ability to start ParseServer with `ParseServer.start(options)`, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Adds request original IP to cloud code hooks, thanks to [Gustav Ahlberg](https://github.com/Gyran)
* Corrects some outdated links, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Adds serverURL validation on startup, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Adds ability to login with POST requests alongside GET, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
* Adds ability to login with email, instead of username, thanks to [Florent Vilmart](https://github.com/flovilmart)
#### Bug Fixes:
* Fixes issue affecting beforeSaves and increments, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
#### Dependency Updates:
* [parse-server-push-adapter@2.0.2](https://www.npmjs.com/package/parse-server-push-adapter)
* [semver@5.4.1](https://www.npmjs.com/package/semver)
* [pg-promise@7.0.3](https://www.npmjs.com/package/pg-promise)
* [mongodb@2.2.33](https://www.npmjs.com/package/mongodb)
* [parse@1.10.1](https://www.npmjs.com/package/parse)
* [express@4.16.0](https://www.npmjs.com/package/express)
* [mime@1.4.1](https://www.npmjs.com/package/mime)
* [parse-server-simple-mailgun-adapter@1.0.1](https://www.npmjs.com/package/parse-server-simple-mailgun-adapter)
##### Devevelopment Dependencies Updates:
* [babel-preset-env@1.6.1](https://www.npmjs.com/package/babel-preset-env)
* [cross-env@5.1.0](https://www.npmjs.com/package/cross-env)
* [mongodb-runner@3.6.1](https://www.npmjs.com/package/mongodb-runner)
* [eslint-plugin-flowtype@2.39.1](https://www.npmjs.com/package/eslint-plugin-flowtype)
* [eslint@4.9.0](https://www.npmjs.com/package/eslint)
### 2.6.3
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.2...2.6.3)
#### Improvements:
* Queries on Pointer fields with `$in` and `$nin` now supports list of objectId's, thanks to [Florent Vilmart](https://github.com/flovilmart)
* LiveQueries on `$in` and `$nin` for pointer fields work as expected thanks to [Florent Vilmart](https://github.com/flovilmart)
* Also remove device token when APNS error is BadDeviceToken, thanks to [Mauricio Tollin](https://github.com/)
* LRU cache is not available on the ParseServer object, thanks to [Tyler Brock](https://github.com/tbrock)
* Error messages are more expressive, thanks to [Tyler Brock](https://github.com/tbrock)
* Postgres: Properly handle undefined field values, thanks to [Diamond Lewis](https://github.com/dlewis)
* Updating with two GeoPoints fails correctly, thanks to [Anthony Mosca](https://github.com/aontas)
#### New Features:
* Adds ability to set a maxLimit on server configuration for queries, thanks to [Chris Norris](https://github.com/)
#### Bug fixes:
* Fixes issue affecting reporting `_PushStatus` with misconfigured serverURL, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Fixes issue affecting deletion of class that doesn't exist, thanks to [Diamond Lewis](https://github.com/dlewis)
#### Dependency Updates:
* [winston@2.4.0](https://www.npmjs.com/package/winston)
* [pg-promise@6.10.2](https://www.npmjs.com/package/pg-promise)
* [winston-daily-rotate-file@1.6.0](https://www.npmjs.com/package/winston-daily-rotate-file)
* [request@2.83.0](https://www.npmjs.com/package/request)
* [body-parser@1.18.2](https://www.npmjs.com/package/body-parser)
##### Devevelopment Dependencies Updates:
* [request-promise@4.2.2](https://www.npmjs.com/package/request-promise)
* [eslint@4.7.1](https://www.npmjs.com/package/eslint)
### 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)
@@ -647,7 +11,7 @@ This release addresses only that issue.
#### 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 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
@@ -700,7 +64,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
@@ -747,7 +111,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)
@@ -1149,7 +513,7 @@ Other fixes by [Mathias Rangel Wulff](https://github.com/mathiasrw)
* Fix: Improve compatability of cloud code beforeSave hook for newly created object
* Fix: ACL creation for master key only objects
* Fix: Allow uploading files without Content-Type
* Fix: Add features to http request to match Parse.com
* Fix: Add features to http requrest to match Parse.com
* Fix: Bugs in development script when running from locations other than project root
* Fix: Can pass query constraints in URL
* Fix: Objects with legacy "_tombstone" key now don't cause issues.
-46
View File
@@ -1,46 +0,0 @@
# 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/
+5 -22
View File
@@ -6,28 +6,11 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
##### Please Do's
* Begin by reading the [Development Guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) to learn how to get started running the parse-server.
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run:
* **Windows**: `npm run coverage:win`
* **Unix**: `npm run coverage`
* Run the tests for the file you are working on with the following command:
* **Windows**: `npm run test:win spec/MyFile.spec.js`
* **Unix**: `npm test spec/MyFile.spec.js`
* Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at *<PROJECT_ROOT>/coverage/lcov-report/index.html*.
* Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI.
* **Do not** publish the *lib* folder.
##### Run your tests against Postgres (optional)
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
* 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.
##### Code of Conduct
This project adheres to the [Contributor Covenant Code of Conduct](https://github.com/parse-community/parse-server/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to honor this code.
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.
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:carbon
FROM node:boron
RUN mkdir -p /parse-server
COPY ./ /parse-server/
-4
View File
@@ -28,7 +28,3 @@ 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,7 +31,3 @@ 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.
+31 -150
View File
@@ -1,45 +1,18 @@
![Parse Server logo](.github/parse-server-logo.png?raw=true)
[![Backers on Open Collective](https://opencollective.com/parse-server/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/parse-server/sponsors/badge.svg)](#sponsors)
[![Build Status](https://img.shields.io/travis/parse-community/parse-server/master.svg?style=flat)](https://travis-ci.org/parse-community/parse-server)
[![Coverage Status](https://img.shields.io/codecov/c/github/parse-community/parse-server/master.svg)](https://codecov.io/github/parse-community/parse-server?branch=master)
[![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)
[![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)
[![Greenkeeper badge](https://badges.greenkeeper.io/parse-community/parse-server.svg)](https://greenkeeper.io/)
Parse Server is an [open source version of the Parse backend](http://blog.parseplatform.org/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
[![Join Chat](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/ParsePlatform/Chat)
Parse Server is an [open source version of the Parse backend](http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.
- [Getting Started](#getting-started)
- [Running Parse Server](#running-parse-server)
- [Locally](#locally)
- [Docker](#inside-a-docker-container)
- [Saving an Object](#saving-your-first-object)
- [Connect an SDK](#connect-your-app-to-parse-server)
- [Running elsewhere](#running-parse-server-elsewhere)
- [Sample Application](#parse-server-sample-application)
- [Parse Server + Express](#parse-server--express)
- [Logging](#logging)
- [Documentation](#documentation)
- [Configuration](#configuration)
- [Basic Options](#basic-options)
- [Client Key Options](#client-key-options)
- [Advanced Options](#advanced-options)
- [Logging](#logging-1)
- [Email Verification & Password Reset](#email-verification-and-password-reset)
- [Using Environment Variables](#using-environment-variables-to-configure-parse-server)
- [Available Adapters](#available-adapters)
- [Configuring File Adapters](#configuring-file-adapters)
- [Support](#support)
- [Ride the Bleeding Edge](#want-to-ride-the-bleeding-edge)
- [Contributing](#contributing)
- [Backers](#backers)
- [Sponsors](#sponsors)
# Getting Started
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parseplatform.org/learn/parse-server-video-series-april-2016/](http://blog.parseplatform.org/learn/parse-server-video-series-april-2016/)
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.
@@ -51,14 +24,12 @@ $ 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
$ docker run --name my-parse-server --link my-mongo:mongo 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.
@@ -69,7 +40,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](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:
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:
```bash
curl -X POST \
@@ -131,19 +102,19 @@ $ curl -X GET \
```
To learn more about using saving and querying objects on Parse Server, check out the [Parse documentation](http://docs.parseplatform.org).
To learn more about using saving and querying objects on Parse Server, check out the [Parse documentation](https://parse.com/docs).
### Connect your app to Parse Server
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://github.com/parse-community/parse-server/wiki/Parse-Server-Guide#using-parse-sdks-with-parse-server).
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://github.com/ParsePlatform/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/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.
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.
### Parse Server Sample Application
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:
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:
* [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)
@@ -154,7 +125,6 @@ We have provided a basic [Node.js application](https://github.com/parse-communit
* [Pivotal Web Services](https://github.com/cf-platform-eng/pws-parse-server)
* [Back4app](http://blog.back4app.com/2016/03/01/quick-wizard-migration/)
* [Gomix](https://gomix.com/#!/project/parse-server)
* [Flynn](https://flynn.io/blog/parse-apps-on-flynn)
### Parse Server + Express
@@ -186,7 +156,7 @@ For a full list of available options, run `parse-server --help`.
## Logging
Parse Server will, by default, log:
Parse Server will, by default, will log:
* to the console
* daily rotating files as new line delimited JSON
@@ -196,13 +166,15 @@ 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/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.
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).
## Configuration
@@ -218,7 +190,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 quick start](http://docs.parseplatform.org/parse-server/guide/#push-notifications_push-notifications-quick-start).
* `push` - Configuration options for APNS and GCM push. See the [Push Notifications wiki entry](https://github.com/ParsePlatform/parse-server/wiki/Push).
#### Client key options
@@ -234,23 +206,16 @@ 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.
* `auth` - Used to configure support for [3rd party authentication](http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).
* `oauth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/OAuth).
* `facebookAppIds` - An array of valid Facebook application IDs that users may authenticate with.
* `mountPath` - Mount path for the server. Defaults to `/parse`.
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
* `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)).
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
* `logLevel` - Set the specific level you want to log. Defaults to `info`. The default logger uses the npm log levels as defined by the underlying winston logger. Check [Winston logging levels](https://github.com/winstonjs/winston#logging-levels) for details on values to specify.
* `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)).
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
* `maxLimit` - The maximum value supported for the limit option on queries. Defaults to unlimited.
* `revokeSessionOnPasswordReset` - When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
* `passwordPolicy` - Optional password policy rules to enforce.
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
* `middleware` - (CLI only), a module name, function that is an express middleware. When using the CLI, the express app will load it just **before** mounting parse-server on the mount path. This option is useful for injecting a monitoring middleware.
* `masterKeyIps` - The array of ip addresses where masterKey usage will be restricted to only these ips. (Default to [] which means allow all ips). If you're using this feature and have `useMasterKey: true` in cloudcode, make sure that you put your own ip in this list.
* `readOnlyMasterKey` - A masterKey that has full read access to the data, but no write access. This key should be treated the same way as your masterKey, keeping it private.
* `objectIdSize` - The string length of the newly generated object's ids.
##### Logging
@@ -295,7 +260,7 @@ var server = ParseServer({
appName: 'Parse App',
// The email adapter
emailAdapter: {
module: '@parse/simple-mailgun-adapter',
module: 'parse-server-simple-mailgun-adapter',
options: {
// The address that your emails come from
fromAddress: 'parse@example.com',
@@ -316,7 +281,7 @@ var server = ParseServer({
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
// 1. a RegExp 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) },
@@ -335,10 +300,8 @@ You can also use other email adapters contributed by the community such as:
- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter)
- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter)
- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template)
- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter)
- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter)
- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter)
- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter)
### Using environment variables to configure Parse Server
@@ -350,7 +313,7 @@ PARSE_SERVER_APPLICATION_ID
PARSE_SERVER_MASTER_KEY
PARSE_SERVER_DATABASE_URI
PARSE_SERVER_URL
PARSE_SERVER_CLOUD
PARSE_SERVER_CLOUD_CODE_MAIN
```
The default port is 1337, to use a different port set the PORT environment variable:
@@ -362,12 +325,7 @@ $ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
For the full list of configurable environment variables, run `parse-server --help`.
### Available Adapters
All official adapters are distributed as scoped pacakges on [npm (@parse)](https://www.npmjs.com/search?q=scope%3Aparse).
Some well maintained adapters are also available on the [Parse Server Modules](https://github.com/parse-server-modules) organization.
You can also find more adapters maintained by the community by searching on [npm](https://www.npmjs.com/search?q=parse-server%20adapter&page=1&ranking=optimal).
[Parse Server Modules (Adapters)](https://github.com/parse-server-modules)
### Configuring File Adapters
@@ -377,19 +335,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 guide](http://docs.parseplatform.org/parse-server/guide/#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 wiki](https://github.com/ParsePlatform/parse-server/wiki/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/parse-community/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/ParsePlatform/parse-server/issues):
- [ ] You've met the [prerequisites](http://docs.parseplatform.org/parse-server/guide/#prerequisites).
- [ ] You've met the [prerequisites](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites).
- [ ] You're running the [latest version](https://github.com/parse-community/parse-server/releases) of Parse Server.
- [ ] You're running the [latest version](https://github.com/ParsePlatform/parse-server/releases) of Parse Server.
- [ ] 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.
- [ ] 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.
# Want to ride the bleeding edge?
@@ -406,80 +364,3 @@ 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.
# Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/parse-server#backer)]
<a href="https://opencollective.com/parse-server/backer/0/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/1/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/2/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/3/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/4/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/5/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/6/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/7/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/8/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/9/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/10/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/11/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/12/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/13/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/14/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/15/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/16/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/17/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/18/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/19/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/20/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/21/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/22/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/23/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/24/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/25/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/26/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/27/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/28/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/backer/29/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/29/avatar.svg"></a>
# Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/parse-server#sponsor)]
<a href="https://opencollective.com/parse-server/sponsor/0/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/1/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/2/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/3/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/4/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/5/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/6/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/7/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/8/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/9/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/10/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/11/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/12/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/13/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/14/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/15/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/16/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/17/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/18/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/19/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/20/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/21/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/22/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/23/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/24/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/25/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/26/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/27/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/28/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/parse-server/sponsor/29/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/29/avatar.svg"></a>
-75
View File
@@ -1,75 +0,0 @@
# Parse Community Vulnerability Disclosure Program
If you believe you have found a security vulnerability on one of parse-community maintained packages,
we encourage you to let us know right away.
We will investigate all legitimate reports and do our best to quickly fix the problem.
Before reporting though, please review this page including and those things that should not be reported.
# Responsible Disclosure Policy
If you comply with the policies below when reporting a security issue to parse community,
we will not initiate a lawsuit or law enforcement investigation against you in response to your report.
We ask that:
- You give us reasonable time to investigate and mitigate an issue you report before making public any information about the report or sharing such information with others. This means we request _at least_ **7 days** to get back to you with an initial response and _at least_ **30 days** from initial contact (made by you) to apply a patch.
- You do not interact with an individual account (which includes modifying or accessing data from the account) if the account owner has not consented to such actions.
- You make a good faith effort to avoid privacy violations and disruptions to others, including (but not limited to) destruction of data and interruption or degradation of our services.
- You do not exploit a security issue you discover for any reason. (This includes demonstrating additional risk, such as attempted compromise of sensitive company data or probing for additional issues). You do not violate any other applicable laws or regulations.
# Communicating with us
All vulnerability should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
You can use our PGP public key, which is also uploaded [here](hkp://pgp.mit.edu):
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFoi200BEACnFHa4Atvw62TjpI5uDtyOF1Ab6gd6898ITXlzFVlAoiqTBE2o
S3H9vCe6w32HkTqyKiymdk50mAaDJrEOyAZSqj0gc4r7vmCx2s7f3iO9A9PEGsHj
UROnkJ5v2su1Dk95XQrbrR4JyNvFMLLqEbdK78Mhx/Xd5QqOD8pop0cS8pF1f1Mb
3MiZb3bxFj+7n+KC80C2+CNyJt95alVnq2MuwbEuwTJQV5CEgRqzBCov9qnLgloK
w7YP4YLkKZoMZQ45mWCUTmn8YIa9PabDLXUhlKv3MQInhnJIELb+jSKO96glHr6p
DpFf0pwRfsuoIhy3jaO7K/ws5uZY3/Ae3gjrAlOB8jhae0POWSwEM+iWHg3wcjpt
lRdu/OgPXqKIgMAXw6Kx9XrskEhOI9ZQfHZlK3HL4ArSdtGYIO5pVNeRssljJvJm
G5HJuGBaPCQNYX7BWJkXFF0HYV6Ke2JDXGVSM4ubPJsZcA4Yx7SYyvuOsNgqHOFM
9snfPfAPAhu/4zCYNCO2NNBc7HaH+qwIvveWX5tTGe6UpX/wOcD8xkoEn+UygQbO
lwu+kjFn8H6RlDChPR65aJTU5Lu0kqRnej1gCjYzOS6AjFOjLuRBlRaey+myhQHO
TbII9nkYI3abYhD8Di77Ve6XFMQI1grPGaqNp6ZLfej1u8PExpclzxTbtQARAQAB
tDdQYXJzZVBsYXRmb3JtLm9yZyBTZWN1cml0eSA8c2VjdXJpdHlAcGFyc2VwbGF0
Zm9ybS5vcmc+iQI9BBMBCgAnBQJaIttNAhsDBQkHhh+ABQsJCAcDBRUKCQgLBRYC
AwEAAh4BAheAAAoJEOaNxtHMZ3/L13EP/2X4i0h1Jidr0GN6t7LUJFDBDHxnY+V+
kPXuucWqtFSkiznHNWSKh8N1LY9N+5eYQj21fJw2RV+ePZKb07jcThG8G1qg+tIn
bbLO2RxmuO/ISgqgvpmtZH9FHtjpGaGStCQqljtZDcBI0Y52l9SLItZjoiRlXePb
C+embDhO6Wzgsi+zkzskm8ISw4mTbDY+HN/1TakCsfMMs8J7twQar+eW97WibzRW
pvhIeJ6egzxs2dW0bXxb0OPvx9xm/FxxRgf+2vySYFnZ88j7Luur4VKdBvh1NtUT
n+xLp/heCHQ/If1ou1Dd6rypEng3A75WANf5m1TAd8JrlQZEOLm4oW+brpDBamqc
dP2z2/zzKykZzRoN1PSX9JRsyLhJhZSXIm/49gATtkaUFBBsFrrwqdWTV7tWFmQI
Oe2wT9IExe+RmXsVqwIndyVkcROuXXGt3y92dcmEriNUGmYXJchs9pAdOCkpHqmf
BkZWalXuWdWiwshNiEF3KWiNbw6BMEWlKPuuJ6kWevLj35MQMLIIlGLx722/kbBB
KIJfp8NdWncIyx0ulq70GWdNaKvu+9dDElMCsPqXeVxbXVpGiBcIgX1S0JEU0YX7
kShAAkZDwhyfUh4IYbyJbNVNzhMazDqI48kP8Kz8vqvZAf3SXyjSBL1XKWLAfKtb
Bs4OuHR3FDkjuQINBFoi200BEADoh8xHW4SVuVnF+Y82Z2B25Ybhwd13mP3LUxwX
cRYeyVge8V7kyftYiUbIybDoxa3H8ysiSgH51yHkoRsq8JO3WEnJZop1ZnjOKhjv
g73vciKImAGrsTVtUR4ErwUsHaXgCFmlmyTI/RLPDfvHdZyYm9wji4XLZrFu5O/s
JD1wU1/aMYCBRjrszI4uklyqGUx2kKLYEI7iITAVVQ8/xFlPPYhVX02jR7fCqTkI
ualVAzK/6Lc4VGqjzFG5kElLCIVDTMLJvPETgXeSAuBigcC+osTw8IRhqY87qNbn
ZpGDKJxV7ARXg5akg/98xI1TZiAMP1vHVKke7XiH5E6M/3IyPiY5ns4SaXu3ZonN
EUR9Z60IeUHVkGpMUk0K0avkaLIAWb93D6jlZ+fJoNBuCoWB31d6+/W8LnrocTQb
mL9ls8HiabrPDsuzI9y3ptOjgGn8pmWoJHM2fiQ5eElexHFB+1QTqq1yiTnuohi/
p+BbKK6V/FZbrP4Rd3hOmPkOWag3KvwGFF8vW6gmx3lft+B90TtBSMv46lqDI5TX
VUr3Un9YHIF+xQMNAgZuJkTIVLrYgexRHnJGrwhRXSZgpVkvj/ygIhR2EvY8zySh
/EFw9ZTmhOrR34q/qlChEuUriP1cc1ygTnjfcw+ZgsTPh50VZk8s5cZAa8tVFINR
cZKBSwARAQABiQIlBBgBCgAPBQJaIttNAhsMBQkHhh+AAAoJEOaNxtHMZ3/LgbUQ
AJHyhtH3bkQsWH0Z+PonbcNxZNq1ZlOfosQIWRzX8BioJpQ5qttkKt5PwhBDrr9h
4ySYzsbTg3fSkIkjfUfaRr+62xay5efaIwG5PcHdXOR/fKCJ6NrTvMEzPUZ8521e
yk0rjm++bNqh+Knez0+xnk99n1XlPrMAXrEb63oM5IBnpJC1PcztzMhdvsNw+B/l
2YcVhXH2peOp/GbAd58fB7JMggwumCktFnXYsYZlMFch3mwKDb66Qi2gbCgK2KHU
sj/mPag7vpy1E4lOlBnDeZpYnjfE/8VJkT4ck5OAwywZK/NUqLfh35RaIwjeXDLM
nlLff3HQJPXYzavCzLZ5dMZJfn0968NIHitjoW9VLs9UhrY7EWI7T6GAMX3wHcq6
ssGOkS0Y1OW8s7jFuoe00PByjiCHcFjBG2NF3n08Nu2c5hewGPs0FdhTadQtHpI2
TTeSIxQ2Ui21UfcX4wMbqELh871ZeQcbVp5LWWibVbfy4mx4Tq/Hvgp7DeBh8DLF
/7MDDwZ+RIBoy98CYz4xsFMdS/9L64uBk/0C+U4OwFJI1FDDxFp6cDqjxdykWi48
wsCczfashguiuJeJ1Ug8URRLY/DKQmjSJaCwy3McK/MOb+JVMazMUyrU9XaGuP4Y
Co6fHPyjrvmE5DtU5Vp8O68ZpOYrkM6X22dIQpPi6atm
=o7Nx
-----END PGP PUBLIC KEY BLOCK-----
```
+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\n" "${INSTALL_DIR}"
printf "Setting up parse-server in %s" "${INSTALL_DIR}\n"
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}"
-12921
View File
File diff suppressed because it is too large Load Diff
+53 -59
View File
@@ -1,11 +1,11 @@
{
"name": "parse-server",
"version": "2.8.2",
"version": "2.3.0",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/parse-community/parse-server"
"url": "https://github.com/ParsePlatform/parse-server"
},
"files": [
"bin/",
@@ -14,82 +14,76 @@
"views/",
"LICENSE",
"PATENTS",
"postinstall.js",
"README.md"
],
"license": "BSD-3-Clause",
"dependencies": {
"@parse/fs-files-adapter": "1.0.1",
"@parse/push-adapter": "3.0.0-alpha2",
"@parse/s3-files-adapter": "1.2.1",
"@parse/simple-mailgun-adapter": "1.0.2",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"commander": "2.15.1",
"deepcopy": "1.0.0",
"express": "4.16.2",
"bcryptjs": "2.3.0",
"body-parser": "1.15.2",
"commander": "2.9.0",
"deepcopy": "0.6.3",
"express": "4.14.0",
"intersect": "1.0.1",
"lodash": "4.17.5",
"lru-cache": "4.1.2",
"mime": "2.3.1",
"mongodb": "3.0.7",
"multer": "1.3.0",
"parse": "1.11.1",
"pg-promise": "8.4.0",
"redis": "2.8.0",
"request": "2.85.0",
"semver": "5.5.0",
"tv4": "1.3.0",
"uuid": "^3.1.0",
"winston": "2.4.1",
"winston-daily-rotate-file": "1.7.2",
"ws": "5.2.0"
"lodash": "4.17.2",
"lru-cache": "4.0.2",
"mime": "1.3.4",
"mongodb": "2.2.10",
"multer": "1.2.0",
"parse": "1.9.2",
"parse-server-fs-adapter": "1.0.1",
"parse-server-push-adapter": "1.1.0",
"parse-server-s3-adapter": "1.0.6",
"parse-server-simple-mailgun-adapter": "1.0.0",
"pg-promise": "5.4.5",
"redis": "2.6.3",
"request": "2.79.0",
"semver": "5.2.0",
"tv4": "1.2.7",
"winston": "2.3.0",
"winston-daily-rotate-file": "1.4.0",
"ws": "1.1.1"
},
"devDependencies": {
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-eslint": "^8.2.2",
"babel-plugin-transform-flow-strip-types": "6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "1.6.1",
"babel-cli": "6.18.0",
"babel-core": "6.18.2",
"babel-eslint": "^7.1.1",
"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",
"bcrypt-nodejs": "0.0.3",
"cross-env": "5.1.6",
"deep-diff": "1.0.1",
"eslint": "^4.9.0",
"eslint-plugin-flowtype": "^2.39.1",
"flow-bin": "^0.73.0",
"gaze": "1.1.3",
"jasmine": "3.1.0",
"jasmine-spec-reporter": "^4.1.0",
"mongodb-runner": "3.6.1",
"nodemon": "1.17.5",
"nyc": "^11.0.2",
"request-promise": "4.2.2"
"cross-env": "3.1.3",
"deep-diff": "0.3.4",
"eslint": "^3.10.2",
"eslint-plugin-flowtype": "^2.25.0",
"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"
},
"scripts": {
"dev": "npm run build && node bin/dev",
"lint": "flow && eslint --cache ./",
"build": "babel src/ -d lib/ --copy-files",
"lint": "eslint --cache ./",
"build": "babel src/ -d lib/",
"pretest": "npm run lint",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 $COVERAGE_OPTION jasmine",
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 jasmine",
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/istanbul cover' npm test",
"coverage:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/jasmine/bin/jasmine.js",
"start": "node ./bin/parse-server",
"prepare": "npm run build",
"postinstall": "node -p 'require(\"./postinstall.js\")()'"
"prepublish": "npm run build"
},
"engines": {
"node": ">= 8"
"node": ">=4.5"
},
"bin": {
"parse-server": "./bin/parse-server"
},
"optionalDependencies": {
"bcrypt": "2.0.1",
"uws": "^10.148.0"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/parse-server",
"logo": "https://opencollective.com/parse-server/logo.txt?reverse=true&variant=binary"
"bcrypt": "1.0.0"
}
}
-50
View File
@@ -1,50 +0,0 @@
const pkg = require('./package.json');
const version = parseFloat(process.version.substr(1));
const minimum = parseFloat(pkg.engines.node.match(/\d+/g).join('.'));
module.exports = function () {
const openCollective = `
1111111111
1111111111111111
1111111111111111111111
11111111111111111111111111
111111111111111 11111111
1111111111111 111111
1111111111111 111111111 111111
111111111111 11111111111 111111
1111111111111 11111111111 111111
1111111111111 1111111111 111111
1111111111111111111111111 1111111
11111111 11111111
111111 1111111111111111111
11111 11111 111111111111111111
11111 11111111111111111
111111 111111111111111111
11111111111111111111111111
1111111111111111111111
111111111111111111
11111111111
Thanks for installing parse 🙏
Please consider donating to our open collective
to help us maintain this package.
👉 https://opencollective.com/parse-server
`;
process.stdout.write(openCollective);
if (version >= minimum) {
process.exit(0);
}
const errorMessage = `
⚠️ parse-server requires at least node@${minimum}!
You have node@${version}
`;
process.stdout.write(errorMessage);
process.exit(1);
};
-2
View File
@@ -35,8 +35,6 @@
padding: 0 0 0 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Invalid Link</h1>
@@ -1,68 +0,0 @@
<!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
@@ -1,45 +0,0 @@
<!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
@@ -1,45 +0,0 @@
<!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>
-233
View File
@@ -1,233 +0,0 @@
/**
* Parse Server Configuration Builder
*
* This module builds the definitions file (src/Options/Definitions.js)
* from the src/Options/index.js options interfaces.
* The Definitions.js module is responsible for the default values as well
* as the mappings for the CLI.
*
* To rebuild the definitions file, run
* `$ node resources/buildConfigDefinitions.js`
*/
const parsers = require('../src/Options/parsers');
function last(array) {
return array[array.length - 1];
}
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
function toENV(key) {
let str = '';
let previousIsUpper = false;
for(let i = 0; i < key.length; i++) {
const char = key[i];
if (letters.indexOf(char) >= 0) {
if (!previousIsUpper) {
str += '_';
previousIsUpper = true;
}
} else {
previousIsUpper = false;
}
str += char;
}
return str.toUpperCase();
}
function getCommentValue(comment) {
if (!comment) { return }
return comment.value.trim();
}
function getENVPrefix(iface) {
if (iface.id.name === 'ParseServerOptions') {
return 'PARSE_SERVER_';
}
if (iface.id.name === 'CustomPagesOptions') {
return 'PARSE_SERVER_CUSTOM_PAGES_';
}
if (iface.id.name === 'LiveQueryServerOptions') {
return 'PARSE_LIVE_QUERY_SERVER_';
}
if (iface.id.name === 'LiveQueryOptions') {
return 'PARSE_SERVER_LIVEQUERY_';
}
}
function processProperty(property, iface) {
const firstComment = getCommentValue(last(property.leadingComments || []));
const lastComment = getCommentValue((property.trailingComments || [])[0]);
const name = property.key.name;
const prefix = getENVPrefix(iface);
if (!firstComment) {
return;
}
const components = firstComment.split(':ENV:').map((elt) => {
return elt.trim();
});
let defaultValue;
if (lastComment && lastComment.indexOf('=') >= 0) {
const slice = lastComment.slice(lastComment.indexOf('=') + 1, lastComment.length).trim();
defaultValue = slice;
}
const help = components[0];
const env = components[1] || (prefix + toENV(name));
let type = property.value.type;
let isRequired = true;
if (type == 'NullableTypeAnnotation') {
isRequired = false;
type = property.value.typeAnnotation.type;
}
return {
name,
env,
help,
type,
defaultValue,
types: property.value.types,
typeAnnotation: property.value.typeAnnotation,
required: isRequired
};
}
function doInterface(iface) {
return iface.body.properties
.map((prop) => processProperty(prop, iface))
.filter((e) => e !== undefined);
}
function mapperFor(elt, t) {
const p = t.identifier('parsers');
const wrap = (identifier) => t.memberExpression(p, identifier);
if (t.isNumberTypeAnnotation(elt)) {
return t.callExpression(wrap(t.identifier('numberParser')), [t.stringLiteral(elt.name)]);
} else if (t.isArrayTypeAnnotation(elt)) {
return wrap(t.identifier('arrayParser'));
} else if (t.isAnyTypeAnnotation(elt)) {
return wrap(t.identifier('objectParser'));
} else if (t.isBooleanTypeAnnotation(elt)) {
return wrap(t.identifier('booleanParser'));
} else if (t.isGenericTypeAnnotation(elt)) {
const type = elt.typeAnnotation.id.name;
if (type == 'Adapter') {
return wrap(t.identifier('moduleOrObjectParser'));
}
if (type == 'NumberOrBoolean') {
return wrap(t.identifier('numberOrBooleanParser'));
}
return wrap(t.identifier('objectParser'));
}
}
function parseDefaultValue(elt, value, t) {
let litteralValue;
if (t.isStringTypeAnnotation(elt)) {
if (value == '""' || value == "''") {
litteralValue = t.stringLiteral('');
} else {
litteralValue = t.stringLiteral(value);
}
} else if (t.isNumberTypeAnnotation(elt)) {
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
} else if (t.isArrayTypeAnnotation(elt)) {
const array = parsers.objectParser(value);
litteralValue = t.arrayExpression(array.map((value) => {
if (typeof value == 'string') {
return t.stringLiteral(value);
} else {
throw new Error('Unable to parse array');
}
}));
} else if (t.isAnyTypeAnnotation(elt)) {
litteralValue = t.arrayExpression([]);
} else if (t.isBooleanTypeAnnotation(elt)) {
litteralValue = t.booleanLiteral(parsers.booleanParser(value));
} else if (t.isGenericTypeAnnotation(elt)) {
const type = elt.typeAnnotation.id.name;
if (type == 'NumberOrBoolean') {
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
}
if (type == 'CustomPagesOptions') {
const object = parsers.objectParser(value);
const props = Object.keys(object).map((key) => {
return t.objectProperty(key, object[value]);
});
litteralValue = t.objectExpression(props);
}
}
return litteralValue;
}
function inject(t, list) {
return list.map((elt) => {
if (!elt.name) {
return;
}
const props = ['env', 'help'].map((key) => {
if (elt[key]) {
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(elt[key]));
}
}).filter((e) => e !== undefined);
if (elt.required) {
props.push(t.objectProperty(t.stringLiteral('required'), t.booleanLiteral(true)))
}
const action = mapperFor(elt, t);
if (action) {
props.push(t.objectProperty(t.stringLiteral('action'), action))
}
if (elt.defaultValue) {
const parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
if (parsedValue) {
props.push(t.objectProperty(t.stringLiteral('default'), parsedValue));
} else {
throw new Error(`Unable to parse value for ${elt.name} `);
}
}
const obj = t.objectExpression(props);
return t.objectProperty(t.stringLiteral(elt.name), obj);
}).filter((elt) => {
return elt != undefined;
});
}
const makeRequire = function(variableName, module, t) {
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
return t.variableDeclaration('var', [decl])
}
const plugin = function (babel) {
const t = babel.types;
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
return {
visitor: {
Program: function(path) {
// Inject the parser's loader
path.unshiftContainer('body', makeRequire('parsers', './parsers', t));
},
ExportDeclaration: function(path) {
// Export declaration on an interface
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
const l = inject(t, doInterface(path.node.declaration));
const id = path.node.declaration.id.name;
const exports = t.memberExpression(moduleExports, t.identifier(id));
path.replaceWith(
t.assignmentExpression('=', exports, t.objectExpression(l))
)
}
}
}
}
};
const auxiliaryCommentBefore = `
**** GENERATED CODE ****
This code has been generated by resources/buildConfigDefinitions.js
Do not edit manually, but update Options/index.js
`
const babel = require("babel-core");
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin ], auxiliaryCommentBefore });
require('fs').writeFileSync('./src/Options/Definitions.js', res.code + '\n');
+3
View File
@@ -0,0 +1,3 @@
docker login -e="$DOCKER_EMAIL" -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
docker build -t parseplatform/parse-server:$TRAVIS_TAG .
docker push parseplatform/parse-server:$TRAVIS_TAG
+2 -9
View File
@@ -9,12 +9,6 @@
# From: https://github.com/graphql/graphql-js/blob/master/resources/npm-git.sh
BUILD_DIR=latest
BRANCH="${TRAVIS_BRANCH}"
TARGET="latest"
if [ "$BRANCH" != "master" ];
then
TARGET="$BRANCH-preview"
fi
npm run build
@@ -25,7 +19,6 @@ cp README.md $BUILD_DIR/
cp LICENSE $BUILD_DIR/
cp PATENTS $BUILD_DIR/
cp CHANGELOG.md $BUILD_DIR/
cp postinstall.js $BUILD_DIR/
cp -R lib $BUILD_DIR
cp -R bin $BUILD_DIR
cp -R public_html $BUILD_DIR
@@ -36,5 +29,5 @@ git init
git config user.name "Travis CI"
git config user.email "github@fb.com"
git add .
git commit -m "Deploy $BRANCH to $TARGET branch"
git push --force --quiet "https://${GH_TOKEN}@github.com/parse-community/parse-server.git" master:$TARGET
git commit -m "Deploy master to LATEST branch"
git push --force --quiet "https://${GH_TOKEN}@github.com/parseplatform/parse-server.git" master:latest
+102
View File
@@ -0,0 +1,102 @@
"""
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)
+1 -4
View File
@@ -14,10 +14,8 @@
"Container": true,
"equal": true,
"notEqual": true,
"it_only_db": true,
"it_exclude_dbs": true,
"describe_only_db": true,
"describe_only": true,
"on_db": true,
"defaultConfiguration": true,
"expectSuccess": true,
@@ -28,7 +26,6 @@
"arrayContains": true
},
"rules": {
"no-console": [0],
"no-var": "error"
"no-console": [0]
}
}
+183 -183
View File
@@ -2,32 +2,32 @@
const Config = require("../src/Config");
const loginWithWrongCredentialsShouldFail = function(username, password) {
var loginWithWrongCredentialsShouldFail = function(username, password) {
return new Promise((resolve, reject) => {
Parse.User.logIn(username, password)
.then(() => reject('login should have failed'))
.catch(err => {
if (err.message === 'Invalid username/password.') {
resolve();
} else {
reject(err);
}
});
});
};
var isAccountLockoutError = function(username, password, duration, waitTime) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Parse.User.logIn(username, password)
.then(() => reject('login should have failed'))
.catch(err => {
if (err.message === 'Invalid username/password.') {
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
resolve();
} else {
reject(err);
}
});
});
};
const isAccountLockoutError = function(username, password, duration, waitTime) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Parse.User.logIn(username, password)
.then(() => reject('login should have failed'))
.catch(err => {
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
resolve();
} else {
reject(err);
}
});
}, waitTime);
});
};
@@ -39,26 +39,26 @@ describe("Account Lockout Policy: ", () => {
appName: 'unlimited',
publicServerURL: 'http://localhost:1337/1',
})
.then(() => {
const 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();
});
.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(() => {
Config.get('test');
fail('set duration to an invalid number test failed');
.then(() => {
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') {
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();
}
});
} 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(() => {
Config.get('test');
fail('set threshold to an invalid number test failed');
.then(() => {
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') {
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();
}
});
} 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(() => {
Config.get('test');
fail('threshold value < 1 is invalid test failed');
.then(() => {
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') {
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();
}
});
} 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(() => {
Config.get('test');
fail('threshold value > 999 is invalid test failed');
.then(() => {
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') {
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();
}
});
} 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(() => {
Config.get('test');
fail('duration value < 1 is invalid test failed');
.then(() => {
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') {
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();
}
});
} 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(() => {
Config.get('test');
fail('duration value > 99999 is invalid test failed');
.then(() => {
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') {
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();
}
});
} 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(() => {
const 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();
});
.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();
});
});
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(() => {
const 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(() => {
.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();
});
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(() => {
const 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(() => {
.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();
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();
});
});
})
+13 -13
View File
@@ -1,9 +1,9 @@
const AdaptableController = require("../src/Controllers/AdaptableController").AdaptableController;
const FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
const FilesController = require("../src/Controllers/FilesController").FilesController;
var AdaptableController = require("../src/Controllers/AdaptableController").AdaptableController;
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
var FilesController = require("../src/Controllers/FilesController").FilesController;
const MockController = function(options) {
var MockController = function(options) {
AdaptableController.call(this, options);
}
MockController.prototype = Object.create(AdaptableController.prototype);
@@ -11,8 +11,8 @@ MockController.prototype.constructor = AdaptableController;
describe("AdaptableController", ()=>{
it("should use the provided adapter", (done) => {
const adapter = new FilesAdapter();
const controller = new FilesController(adapter);
var adapter = new FilesAdapter();
var controller = new FilesController(adapter);
expect(controller.adapter).toBe(adapter);
// make sure _adapter is private
expect(controller._adapter).toBe(undefined);
@@ -23,7 +23,7 @@ describe("AdaptableController", ()=>{
});
it("should throw when creating a new mock controller", (done) => {
const adapter = new FilesAdapter();
var adapter = new FilesAdapter();
expect(() => {
new MockController(adapter);
}).toThrow();
@@ -32,9 +32,9 @@ describe("AdaptableController", ()=>{
it("should fail setting the wrong adapter to the controller", (done) => {
function WrongAdapter() {}
const adapter = new FilesAdapter();
const controller = new FilesController(adapter);
const otherAdapter = new WrongAdapter();
var adapter = new FilesAdapter();
var controller = new FilesController(adapter);
var otherAdapter = new WrongAdapter();
expect(() => {
controller.adapter = otherAdapter;
}).toThrow();
@@ -43,7 +43,7 @@ describe("AdaptableController", ()=>{
it("should fail to instantiate a controller with wrong adapter", (done) => {
function WrongAdapter() {}
const adapter = new WrongAdapter();
var adapter = new WrongAdapter();
expect(() => {
new FilesController(adapter);
}).toThrow();
@@ -58,7 +58,7 @@ describe("AdaptableController", ()=>{
});
it("should accept an object adapter", (done) => {
const adapter = {
var adapter = {
createFile: function() { },
deleteFile: function() { },
getFileData: function() { },
@@ -77,7 +77,7 @@ describe("AdaptableController", ()=>{
AGoodAdapter.prototype.getFileData = function() { };
AGoodAdapter.prototype.getFileLocation = function() { };
const adapter = new AGoodAdapter();
var adapter = new AGoodAdapter();
expect(() => {
new FilesController(adapter);
}).not.toThrow();
+27 -53
View File
@@ -1,16 +1,15 @@
const loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
const FilesAdapter = require("@parse/fs-files-adapter").default;
const S3Adapter = require("@parse/s3-files-adapter").default;
const ParsePushAdapter = require("@parse/push-adapter").default;
const Config = require('../src/Config');
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;
describe("AdapterLoader", ()=>{
it("should instantiate an adapter from string in object", (done) => {
const adapterPath = require('path').resolve("./spec/MockAdapter");
var adapterPath = require('path').resolve("./spec/MockAdapter");
const adapter = loadAdapter({
var adapter = loadAdapter({
adapter: adapterPath,
options: {
key: "value",
@@ -25,16 +24,16 @@ describe("AdapterLoader", ()=>{
});
it("should instantiate an adapter from string", (done) => {
const adapterPath = require('path').resolve("./spec/MockAdapter");
const adapter = loadAdapter(adapterPath);
var adapterPath = require('path').resolve("./spec/MockAdapter");
var adapter = loadAdapter(adapterPath);
expect(adapter instanceof Object).toBe(true);
done();
});
it("should instantiate an adapter from string that is module", (done) => {
const adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
const adapter = loadAdapter({
var adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
var adapter = loadAdapter({
adapter: adapterPath
});
@@ -47,8 +46,8 @@ describe("AdapterLoader", ()=>{
});
it("should instantiate an adapter from npm module", (done) => {
const adapter = loadAdapter({
module: '@parse/fs-files-adapter'
var adapter = loadAdapter({
module: 'parse-server-fs-adapter'
});
expect(typeof adapter).toBe('object');
@@ -60,7 +59,7 @@ describe("AdapterLoader", ()=>{
});
it("should instantiate an adapter from function/Class", (done) => {
const adapter = loadAdapter({
var adapter = loadAdapter({
adapter: FilesAdapter
});
expect(adapter instanceof FilesAdapter).toBe(true);
@@ -68,86 +67,61 @@ describe("AdapterLoader", ()=>{
});
it("should instantiate the default adapter from Class", (done) => {
const adapter = loadAdapter(null, FilesAdapter);
var adapter = loadAdapter(null, FilesAdapter);
expect(adapter instanceof FilesAdapter).toBe(true);
done();
});
it("should use the default adapter", (done) => {
const defaultAdapter = new FilesAdapter();
const adapter = loadAdapter(null, defaultAdapter);
var defaultAdapter = new FilesAdapter();
var adapter = loadAdapter(null, defaultAdapter);
expect(adapter instanceof FilesAdapter).toBe(true);
done();
});
it("should use the provided adapter", (done) => {
const originalAdapter = new FilesAdapter();
const adapter = loadAdapter(originalAdapter);
var originalAdapter = new FilesAdapter();
var adapter = loadAdapter(originalAdapter);
expect(adapter).toBe(originalAdapter);
done();
});
it("should fail loading an improperly configured adapter", (done) => {
const Adapter = function(options) {
var Adapter = function(options) {
if (!options.foo) {
throw "foo is required for that adapter";
}
}
const adapterOptions = {
var adapterOptions = {
param: "key",
doSomething: function() {}
};
expect(() => {
const adapter = loadAdapter(adapterOptions, Adapter);
var adapter = loadAdapter(adapterOptions, Adapter);
expect(adapter).toEqual(adapterOptions);
}).not.toThrow("foo is required for that adapter");
done();
});
it("should load push adapter from options", (done) => {
const options = {
android: {
senderId: 'yolo',
apiKey: 'yolo'
var options = {
ios: {
bundleId: 'bundle.id'
}
}
expect(() => {
const adapter = loadAdapter(undefined, ParsePushAdapter, options);
var adapter = loadAdapter(undefined, ParsePushAdapter, options);
expect(adapter.constructor).toBe(ParsePushAdapter);
expect(adapter).not.toBe(undefined);
}).not.toThrow();
done();
});
it("should load custom push adapter from string (#3544)", (done) => {
const adapterPath = require('path').resolve("./spec/MockPushAdapter");
const options = {
ios: {
bundleId: 'bundle.id'
}
}
const pushAdapterOptions = {
adapter: adapterPath,
options
};
expect(() => {
reconfigureServer({
push: pushAdapterOptions,
}).then(() => {
const config = Config.get(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) => {
const s3Adapter = new S3Adapter("key", "secret", "bucket")
var s3Adapter = new S3Adapter("key", "secret", "bucket")
expect(() => {
const adapter = loadAdapter(s3Adapter, FilesAdapter);
var adapter = loadAdapter(s3Adapter, FilesAdapter);
expect(adapter).toBe(s3Adapter);
}).not.toThrow();
done();
+2 -2
View File
@@ -16,7 +16,7 @@ describe('AnalyticsController', () => {
})
}).then(() => {
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
const lastCall = analyticsAdapter.trackEvent.calls.first();
var lastCall = analyticsAdapter.trackEvent.calls.first();
const args = lastCall.args;
expect(args[0]).toEqual('MyEvent');
expect(args[1]).toEqual({
@@ -44,7 +44,7 @@ describe('AnalyticsController', () => {
})
}).then(() => {
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
const lastCall = analyticsAdapter.appOpened.calls.first();
var lastCall = analyticsAdapter.appOpened.calls.first();
const args = lastCall.args;
expect(args[0]).toEqual({
dimensions: {
-337
View File
@@ -1,337 +0,0 @@
const auth = require('../src/Auth');
const Config = require('../src/Config');
const rest = require('../src/rest');
const AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
describe('AudiencesRouter', () => {
it('uses find condition from request.body', (done) => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
const request = {
config: config,
auth: auth.master(config),
body: {
where: {
query: '{ "test": "android" }'
}
},
query: {},
info: {}
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
const 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) => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
where: {
'query': '{ "test": "android" }'
}
},
info: {}
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
const results = res.response.results;
expect(results.length).toEqual(1);
done();
})
.catch((err) => {
fail(err);
done();
});
});
it('query installations with limit = 0', (done) => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0
},
info: {}
};
Config.get('test');
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
const response = res.response;
expect(response.results.length).toEqual(0);
done();
})
.catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('query installations with count = 1', done => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
count: 1
},
info: {}
};
const 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) => {
const 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) => {
const config = Config.get('test');
const androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
};
const iosAudienceRequest = {
'name': 'Iphone Users',
'query': '{ "test": "ios" }'
};
const request = {
config: config,
auth: auth.master(config),
body: {},
query: {
limit: 0,
count: 1
},
info: {}
};
const router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
})
.then(() => {
return router.handleFind(request);
})
.then((res) => {
const response = res.response;
expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2);
done();
})
.catch((err) => {
fail(JSON.stringify(err));
done();
});
});
it('should create, read, update and delete audiences throw api', (done) => {
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
.then(() => {
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
expect(results.results.length).toEqual(1);
expect(results.results[0].name).toEqual('My Audience');
expect(results.results[0].query.deviceType).toEqual('ios');
Parse._request('GET', `push_audiences/${results.results[0].objectId}`, {}, { useMasterKey: true }).then((results) => {
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 = (Config.get(Parse.applicationId)).database.adapter.database;
const now = new Date();
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
.then((audience) => {
database.collection('test__Audience').updateOne(
{ _id: audience.objectId },
{
$set: {
times_used: 1,
_last_used: now
}
},
{},
(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); })
})
});
});
+11 -11
View File
@@ -1,11 +1,11 @@
describe('Auth', () => {
const Auth = require('../src/Auth.js').Auth;
var Auth = require('../src/Auth.js').Auth;
describe('getUserRoles', () => {
let auth;
let config;
let currentRoles = null;
const currentUserId = 'userId';
var auth;
var config;
var currentRoles = null;
var currentUserId = 'userId';
beforeEach(() => {
currentRoles = ['role:userId'];
@@ -33,10 +33,10 @@ describe('Auth', () => {
it('should get user roles from the cache', (done) => {
auth.getUserRoles()
.then((roles) => {
const firstSet = config.cacheController.role.set.calls.first();
var firstSet = config.cacheController.role.set.calls.first();
expect(firstSet).toEqual(undefined);
const firstGet = config.cacheController.role.get.calls.first();
var firstGet = config.cacheController.role.get.calls.first();
expect(firstGet.args[0]).toEqual(currentUserId);
expect(roles).toEqual(currentRoles);
done();
@@ -44,7 +44,7 @@ describe('Auth', () => {
});
it('should only query the roles once', (done) => {
const loadRolesSpy = spyOn(auth, '_loadRoles').and.callThrough();
var loadRolesSpy = spyOn(auth, '_loadRoles').and.callThrough();
auth.getUserRoles()
.then((roles) => {
expect(roles).toEqual(currentRoles);
@@ -57,7 +57,7 @@ describe('Auth', () => {
expect(config.cacheController.role.get.calls.count()).toEqual(1);
expect(loadRolesSpy.calls.count()).toEqual(1);
const firstGet = config.cacheController.role.get.calls.first();
var firstGet = config.cacheController.role.get.calls.first();
expect(firstGet.args[0]).toEqual(currentUserId);
expect(roles).toEqual(currentRoles);
done();
@@ -79,8 +79,8 @@ describe('Auth', () => {
});
it('should properly handle bcrypt upgrade', (done) => {
const bcryptOriginal = require('bcrypt-nodejs');
const bcryptNew = require('bcryptjs');
var bcryptOriginal = require('bcrypt-nodejs');
var bcryptNew = require('bcryptjs');
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
bcryptNew.compare('my1Long:password', res, function(err, res) {
expect(res).toBeTruthy();
+52 -184
View File
@@ -1,26 +1,22 @@
const request = require('request');
const Config = require("../src/Config");
const defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
const authenticationLoader = require('../src/Adapters/Auth');
const path = require('path');
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", "facebookaccountkit", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
it("Should validate structure of " + providerName, (done) => {
const provider = require("../src/Adapters/Auth/" + providerName);
describe('AuthenticationProviers', 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(()=>{}, ()=>{});
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
jequal(provider.validateAppId("app", "key", {}).constructor, Promise.prototype.constructor);
done();
});
});
const getMockMyOauthProvider = function() {
var getMockMyOauthProvider = function() {
return {
authData: {
id: "12345",
@@ -70,47 +66,34 @@ describe('AuthenticationProviders', function() {
}
});
const createOAuthUser = function(callback) {
return createOAuthUserWithSessionToken(undefined, callback);
}
const createOAuthUserWithSessionToken = function(token, callback) {
const jsonBody = {
var createOAuthUser = function(callback) {
var jsonBody = {
authData: {
myoauth: getMockMyOauthProvider().authData
}
};
const options = {
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
body: JSON.stringify(jsonBody)
};
return new Promise((resolve) => {
request.post(options, (err, res, body) => {
resolve({err, res, body});
if (callback) {
callback(err, res, body);
}
});
});
return request.post(options, callback);
}
it("should create user with REST API", done => {
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
const b = body;
var b = JSON.parse(body);
ok(b.sessionToken);
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
const sessionToken = b.sessionToken;
const q = new Parse.Query("_Session");
var sessionToken = b.sessionToken;
var q = new Parse.Query("_Session");
q.equalTo('sessionToken', sessionToken);
q.first({useMasterKey: true}).then((res) => {
if (!res) {
@@ -128,17 +111,17 @@ describe('AuthenticationProviders', function() {
});
it("should only create a single user with REST API", (done) => {
let objectId;
var objectId;
createOAuthUser((error, response, body) => {
expect(error).toBe(null);
const b = body
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);
const b = body;
var b = JSON.parse(body);
expect(b.objectId).not.toBeNull();
expect(b.objectId).not.toBeUndefined();
expect(b.objectId).toBe(objectId);
@@ -147,24 +130,8 @@ describe('AuthenticationProviders', function() {
});
});
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) => {
const provider = getMockMyOauthProvider();
var provider = getMockMyOauthProvider();
Parse.User._registerAuthenticationProvider(provider);
Parse.User._logInWith("myoauth", {
success: function(model) {
@@ -180,38 +147,38 @@ describe('AuthenticationProviders', function() {
success: function(model) {
ok(!model._isLinked("myoauth"),
"User should not be linked to 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");
"Expiration should be cleared");
// make sure the auth data is properly deleted
const config = Config.get(Parse.applicationId);
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();
.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();
}
});
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");
@@ -230,21 +197,14 @@ describe('AuthenticationProviders', function() {
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');
function validateAuthenticationHandler(authenticatonHandler) {
expect(authenticatonHandler).not.toBeUndefined();
expect(typeof authenticatonHandler.getValidatorForProvider).toBe('function');
expect(typeof authenticatonHandler.getValidatorForProvider).toBe('function');
}
it('properly loads custom adapter', (done) => {
const validAuthData = {
var validAuthData = {
id: 'hello',
token: 'world'
}
@@ -319,96 +279,4 @@ describe('AuthenticationProviders', function() {
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);
});
it('properly loads Facebook accountkit adapter with options', () => {
const options = {
facebookaccountkit: {
appIds: ['a', 'b'],
appSecret: 'secret'
}
};
const {adapter, appIds, providerOptions} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
validateAuthenticationAdapter(adapter);
expect(appIds).toEqual(['a', 'b']);
expect(providerOptions.appSecret).toEqual('secret');
});
it('should fail if Facebook appIds is not configured properly', (done) => {
const options = {
facebookaccountkit: {
appIds: []
}
};
const {adapter, appIds} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
adapter.validateAppId(appIds)
.then(done.fail, err => {
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
done();
})
});
it('should fail to validate Facebook accountkit auth with bad token', (done) => {
const options = {
facebookaccountkit: {
appIds: ['a', 'b']
}
};
const authData = {
id: 'fakeid',
access_token: 'badtoken'
};
const {adapter} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
adapter.validateAuthData(authData)
.then(done.fail, err => {
expect(err.code).toBe(190);
expect(err.type).toBe('OAuthException');
done();
})
});
it('should fail to validate Facebook accountkit auth with bad token regardless of app secret proof', (done) => {
const options = {
facebookaccountkit: {
appIds: ['a', 'b'],
appSecret: 'badsecret'
}
};
const authData = {
id: 'fakeid',
access_token: 'badtoken'
};
const {adapter, providerOptions} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
adapter.validateAuthData(authData, providerOptions)
.then(done.fail, err => {
expect(err.code).toBe(190);
expect(err.type).toBe('OAuthException');
done();
})
});
});
+2 -8
View File
@@ -3,7 +3,7 @@ import commander from '../src/cli/utils/commander';
import definitions from '../src/cli/definitions/parse-server';
import liveQueryDefinitions from '../src/cli/definitions/parse-live-query-server';
const testDefinitions = {
var testDefinitions = {
'arg0': 'PROGRAM_ARG_0',
'arg1': {
env: 'PROGRAM_ARG_1',
@@ -12,7 +12,7 @@ const testDefinitions = {
'arg2': {
env: 'PROGRAM_ARG_2',
action: function(value) {
const intValue = parseInt(value);
var intValue = parseInt(value);
if (!Number.isInteger(intValue)) {
throw 'arg2 is invalid';
}
@@ -157,12 +157,6 @@ describe('definitions', () => {
}
}
});
it('should throw when using deprecated facebookAppIds', () => {
expect(() => {
definitions.facebookAppIds.action()
}).toThrow();
});
});
describe('LiveQuery definitions', () => {
+11 -11
View File
@@ -1,9 +1,9 @@
const CacheController = require('../src/Controllers/CacheController.js').default;
var CacheController = require('../src/Controllers/CacheController.js').default;
describe('CacheController', function() {
let FakeCacheAdapter;
const FakeAppID = 'foo';
const KEY = 'hello';
var FakeCacheAdapter;
var FakeAppID = 'foo';
var KEY = 'hello';
beforeEach(() => {
FakeCacheAdapter = {
@@ -18,7 +18,7 @@ describe('CacheController', function() {
it('should expose role and user caches', (done) => {
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
expect(cache.role).not.toEqual(null);
expect(cache.role.get).not.toEqual(null);
@@ -31,24 +31,24 @@ describe('CacheController', function() {
['role', 'user'].forEach((cacheName) => {
it('should prefix ' + cacheName + ' cache', () => {
const cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
var cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
cache.put(KEY, 'world');
const firstPut = FakeCacheAdapter.put.calls.first();
var firstPut = FakeCacheAdapter.put.calls.first();
expect(firstPut.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
cache.get(KEY);
const firstGet = FakeCacheAdapter.get.calls.first();
var firstGet = FakeCacheAdapter.get.calls.first();
expect(firstGet.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
cache.del(KEY);
const firstDel = FakeCacheAdapter.del.calls.first();
var firstDel = FakeCacheAdapter.del.calls.first();
expect(firstDel.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
});
});
it('should clear the entire cache', () => {
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
cache.clear();
expect(FakeCacheAdapter.clear.calls.count()).toEqual(1);
@@ -64,7 +64,7 @@ describe('CacheController', function() {
FakeCacheAdapter.get = () => Promise.reject();
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
cache.get('foo').then(done, () => {
fail('Promise should not be rejected.');
+62 -62
View File
@@ -1,10 +1,10 @@
const Client = require('../src/LiveQuery/Client').Client;
const ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
var Client = require('../src/LiveQuery/Client').Client;
var ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
describe('Client', function() {
it('can be initialized', function() {
const parseWebSocket = new ParseWebSocket({});
const client = new Client(1, parseWebSocket);
var parseWebSocket = new ParseWebSocket({});
var client = new Client(1, parseWebSocket);
expect(client.id).toBe(1);
expect(client.parseWebSocket).toBe(parseWebSocket);
@@ -12,7 +12,7 @@ describe('Client', function() {
});
it('can push response', function() {
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
Client.pushResponse(parseWebSocket, 'message');
@@ -21,13 +21,13 @@ describe('Client', function() {
});
it('can push error', function() {
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
Client.pushError(parseWebSocket, 1, 'error', true);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('error');
expect(messageJSON.error).toBe('error');
expect(messageJSON.code).toBe(1);
@@ -35,13 +35,13 @@ describe('Client', function() {
});
it('can add subscription information', function() {
const subscription = {};
const fields = ['test'];
const subscriptionInfo = {
var subscription = {};
var fields = ['test'];
var subscriptionInfo = {
subscription: subscription,
fields: fields
}
const client = new Client(1, {});
var client = new Client(1, {});
client.addSubscriptionInfo(1, subscriptionInfo);
expect(client.subscriptionInfos.size).toBe(1);
@@ -49,27 +49,27 @@ describe('Client', function() {
});
it('can get subscription information', function() {
const subscription = {};
const fields = ['test'];
const subscriptionInfo = {
var subscription = {};
var fields = ['test'];
var subscriptionInfo = {
subscription: subscription,
fields: fields
}
const client = new Client(1, {});
var client = new Client(1, {});
client.addSubscriptionInfo(1, subscriptionInfo);
const subscriptionInfoAgain = client.getSubscriptionInfo(1);
var subscriptionInfoAgain = client.getSubscriptionInfo(1);
expect(subscriptionInfoAgain).toBe(subscriptionInfo);
});
it('can delete subscription information', function() {
const subscription = {};
const fields = ['test'];
const subscriptionInfo = {
var subscription = {};
var fields = ['test'];
var subscriptionInfo = {
subscription: subscription,
fields: fields
}
const client = new Client(1, {});
var client = new Client(1, {});
client.addSubscriptionInfo(1, subscriptionInfo);
client.deleteSubscriptionInfo(1);
@@ -78,7 +78,7 @@ describe('Client', function() {
it('can generate ParseObject JSON with null selected field', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -86,13 +86,13 @@ describe('Client', function() {
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
};
const client = new Client(1, {});
var client = new Client(1, {});
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(parseObjectJSON);
});
it('can generate ParseObject JSON with undefined selected field', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -100,13 +100,13 @@ describe('Client', function() {
createdAt: '2015-12-07T21:27:13.746Z',
ACL: 'test',
};
const client = new Client(1, {});
var client = new Client(1, {});
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(parseObjectJSON);
});
it('can generate ParseObject JSON with selected fields', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -115,7 +115,7 @@ describe('Client', function() {
ACL: 'test',
test: 'test'
};
const client = new Client(1, {});
var client = new Client(1, {});
expect(client._toJSONWithFields(parseObjectJSON, ['test'])).toEqual({
className: 'test',
@@ -128,7 +128,7 @@ describe('Client', function() {
});
it('can generate ParseObject JSON with nonexistent selected fields', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -137,8 +137,8 @@ describe('Client', function() {
ACL: 'test',
test: 'test'
};
const client = new Client(1, {});
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, ['name']);
var client = new Client(1, {});
var limitedParseObject = client._toJSONWithFields(parseObjectJSON, ['name']);
expect(limitedParseObject).toEqual({
className: 'test',
@@ -151,48 +151,48 @@ describe('Client', function() {
});
it('can push connect response', function() {
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushConnect();
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('connected');
expect(messageJSON.clientId).toBe(1);
});
it('can push subscribe response', function() {
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushSubscribe(2);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('subscribed');
expect(messageJSON.clientId).toBe(1);
expect(messageJSON.requestId).toBe(2);
});
it('can push unsubscribe response', function() {
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushUnsubscribe(2);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('unsubscribed');
expect(messageJSON.clientId).toBe(1);
expect(messageJSON.requestId).toBe(2);
});
it('can push create response', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -201,14 +201,14 @@ describe('Client', function() {
ACL: 'test',
test: 'test'
};
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushCreate(2, parseObjectJSON);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('create');
expect(messageJSON.clientId).toBe(1);
expect(messageJSON.requestId).toBe(2);
@@ -216,7 +216,7 @@ describe('Client', function() {
});
it('can push enter response', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -225,14 +225,14 @@ describe('Client', function() {
ACL: 'test',
test: 'test'
};
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushEnter(2, parseObjectJSON);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('enter');
expect(messageJSON.clientId).toBe(1);
expect(messageJSON.requestId).toBe(2);
@@ -240,7 +240,7 @@ describe('Client', function() {
});
it('can push update response', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -249,14 +249,14 @@ describe('Client', function() {
ACL: 'test',
test: 'test'
};
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushUpdate(2, parseObjectJSON);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('update');
expect(messageJSON.clientId).toBe(1);
expect(messageJSON.requestId).toBe(2);
@@ -264,7 +264,7 @@ describe('Client', function() {
});
it('can push leave response', function() {
const parseObjectJSON = {
var parseObjectJSON = {
key : 'value',
className: 'test',
objectId: 'test',
@@ -273,14 +273,14 @@ describe('Client', function() {
ACL: 'test',
test: 'test'
};
const parseWebSocket = {
var parseWebSocket = {
send: jasmine.createSpy('send')
};
const client = new Client(1, parseWebSocket);
var client = new Client(1, parseWebSocket);
client.pushLeave(2, parseObjectJSON);
const lastCall = parseWebSocket.send.calls.first();
const messageJSON = JSON.parse(lastCall.args[0]);
var lastCall = parseWebSocket.send.calls.first();
var messageJSON = JSON.parse(lastCall.args[0]);
expect(messageJSON.op).toBe('leave');
expect(messageJSON.clientId).toBe(1);
expect(messageJSON.requestId).toBe(2);
+1 -1
View File
@@ -1,4 +1,4 @@
const ClientSDK = require('../src/ClientSDK');
var ClientSDK = require('../src/ClientSDK');
describe('ClientSDK', () => {
it('should properly parse the SDK versions', () => {
+219 -610
View File
File diff suppressed because it is too large Load Diff
+73 -90
View File
@@ -1,7 +1,8 @@
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const fs = require('fs');
'use strict';
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const fs = require('fs');
const loremFile = __dirname + '/support/lorem.txt';
describe("Cloud Code Logger", () => {
@@ -10,21 +11,21 @@ describe("Cloud Code Logger", () => {
beforeEach(done => {
Parse.User.enableUnsafeCurrentUser();
return reconfigureServer({
// useful to flip to false for fine tuning :).
// useful to flip to false for fine tuning :).
silent: true,
}).then(() => {
return Parse.User.signUp('tester', 'abc')
.then(loggedInUser => user = loggedInUser)
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
.then(() => done())
.then(loggedInUser => user = loggedInUser)
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
.then(() => done())
});
});
// Note that helpers takes care of logout.
// see helpers.js:afterEach
// Note that helpers takes care of logout.
// see helpers.js:afterEach
it("should expose log to functions", done => {
const logController = new LoggerController(new WinstonLoggerAdapter());
var logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.define("loggerTest", (req, res) => {
req.log.info('logTest', 'info log', { info: 'some log' });
@@ -62,18 +63,18 @@ describe("Cloud Code Logger", () => {
});
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));
.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));
});
it("should expose log to trigger", (done) => {
const logController = new LoggerController(new WinstonLoggerAdapter());
var logController = new LoggerController(new WinstonLoggerAdapter());
Parse.Cloud.beforeSave("MyObject", (req, res) => {
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
@@ -119,30 +120,30 @@ describe("Cloud Code Logger", () => {
});
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));
.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));
.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 => {
@@ -152,18 +153,17 @@ describe("Cloud Code Logger", () => {
});
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()
});
.save()
.then(
() => done.fail('this is not supposed to succeed'),
() => 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()
});
});
it('should log cloud function success', done => {
@@ -174,14 +174,14 @@ describe("Cloud Code Logger", () => {
});
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();
});
.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 => {
@@ -192,14 +192,14 @@ describe("Cloud Code Logger", () => {
});
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();
});
.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 {2}Input: {"foo":"bar"}\n {2}Error: {"code":141,"message":"it failed!"}/);
done();
});
});
xit('should log a changed beforeSave indicating a change', done => {
@@ -212,35 +212,18 @@ describe("Cloud Code Logger", () => {
});
new Parse.Object('MyObject')
.save()
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
.then(() => {
// expect the log to indicate that it has changed
/*
.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)));
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
@@ -1,51 +0,0 @@
const DatabaseController = require('../src/Controllers/DatabaseController.js');
const validateQuery = DatabaseController._validateQuery;
describe('DatabaseController', function() {
describe('validateQuery', function() {
it('should restructure simple cases of SERVER-13732', (done) => {
const query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
done();
});
it('should not restructure SERVER-13732 queries with $nears', (done) => {
let query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}};
validateQuery(query);
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) => {
const query = {a: 1, $or: [{$or: [{b: 1}, {b: 2}]},
{$or: [{c: 1}, {c: 2}]}]};
validateQuery(query);
expect(query).toEqual({$or: [{$or: [{b: 1, a: 1}, {b: 2, a: 1}]},
{$or: [{c: 1, a: 1}, {c: 2, a: 1}]}]});
done();
});
it('should reject invalid queries', (done) => {
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
done();
});
it('should accept valid queries', (done) => {
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
done();
});
});
});
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -45,5 +45,5 @@ describe('Enable single schema cache', () => {
});
const fakeRequestForConfig = function() {
return Config.get('test');
return new Config('test');
};
+8 -8
View File
@@ -1,12 +1,12 @@
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
describe('EventEmitterPubSub', function() {
it('can publish and subscribe', function() {
const publisher = EventEmitterPubSub.createPublisher();
const subscriber = EventEmitterPubSub.createSubscriber();
var publisher = EventEmitterPubSub.createPublisher();
var subscriber = EventEmitterPubSub.createSubscriber();
subscriber.subscribe('testChannel');
// Register mock checked for subscriber
let isChecked = false;
var isChecked = false;
subscriber.on('message', function(channel, message) {
isChecked = true;
expect(channel).toBe('testChannel');
@@ -19,12 +19,12 @@ describe('EventEmitterPubSub', function() {
});
it('can unsubscribe', function() {
const publisher = EventEmitterPubSub.createPublisher();
const subscriber = EventEmitterPubSub.createSubscriber();
var publisher = EventEmitterPubSub.createPublisher();
var subscriber = EventEmitterPubSub.createSubscriber();
subscriber.subscribe('testChannel');
subscriber.unsubscribe('testChannel');
// Register mock checked for subscriber
let isCalled = false;
var isCalled = false;
subscriber.on('message', function() {
isCalled = true;
});
@@ -35,7 +35,7 @@ describe('EventEmitterPubSub', function() {
});
it('can unsubscribe not subscribing channel', function() {
const subscriber = EventEmitterPubSub.createSubscriber();
var subscriber = EventEmitterPubSub.createSubscriber();
// Make sure subscriber does not throw exception
subscriber.unsubscribe('testChannel');
+10 -45
View File
@@ -1,65 +1,30 @@
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;
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
var Config = require("../src/Config");
var 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",() =>{
it("should properly expand objects", (done) => {
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
const filesController = new FilesController(gridStoreAdapter)
const result = filesController.expandFilesInObject(config, function(){});
var config = new Config(Parse.applicationId);
var gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
var filesController = new FilesController(gridStoreAdapter)
var result = filesController.expandFilesInObject(config, function(){});
expect(result).toBeUndefined();
const fullFile = {
var fullFile = {
type: '__type',
url: "http://an.url"
}
const anObject = {
var anObject = {
aFile: fullFile
}
filesController.expandFilesInObject(config, anObject);
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();
});
});
})
});
+11 -11
View File
@@ -1,24 +1,24 @@
const MongoClient = require("mongodb").MongoClient;
const GridStore = require("mongodb").GridStore;
var MongoClient = require("mongodb").MongoClient;
var GridStore = require("mongodb").GridStore;
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
const Config = require("../src/Config");
const FilesController = require('../src/Controllers/FilesController').default;
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
var Config = require("../src/Config");
var FilesController = require('../src/Controllers/FilesController').default;
// Small additional tests to improve overall coverage
describe_only_db('mongo')("GridStoreAdapter",() =>{
it("should properly instanciate the GridStore when deleting a file", (done) => {
const databaseURI = 'mongodb://localhost:27017/parse';
const config = Config.get(Parse.applicationId);
const gridStoreAdapter = new GridStoreAdapter(databaseURI);
const filesController = new FilesController(gridStoreAdapter);
var databaseURI = 'mongodb://localhost:27017/parse';
var config = new Config(Parse.applicationId);
var gridStoreAdapter = new GridStoreAdapter(databaseURI);
var filesController = new FilesController(gridStoreAdapter);
// save original unlink before redefinition
const originalUnlink = GridStore.prototype.unlink;
var originalUnlink = GridStore.prototype.unlink;
let gridStoreMode;
var gridStoreMode;
// new unlink method that will capture the mode in which GridStore was opened
GridStore.prototype.unlink = function() {
+17 -17
View File
@@ -1,14 +1,14 @@
'use strict';
const httpRequest = require("../src/cloud-code/httpRequest"),
var httpRequest = require("../src/cloud-code/httpRequest"),
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
bodyParser = require('body-parser'),
express = require("express");
const port = 13371;
const httpRequestServer = "http://localhost:" + port;
var port = 13371;
var httpRequestServer = "http://localhost:"+port;
const app = express();
var app = express();
app.use(bodyParser.json({ 'type': '*/*' }));
app.get("/hello", function(req, res){
res.json({response: "OK"});
@@ -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"}'));
@@ -53,9 +53,9 @@ describe("httpRequest", () => {
});
it("should do /hello with callback and promises", (done) => {
let calls = 0;
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);
@@ -102,9 +102,9 @@ describe("httpRequest", () => {
});
it("should fail on 404", (done) => {
let calls = 0;
var calls = 0;
httpRequest({
url: httpRequestServer + "/404",
url: httpRequestServer+"/404",
success: function() {
calls++;
fail("should not succeed");
@@ -124,7 +124,7 @@ describe("httpRequest", () => {
it("should fail on 404", (done) => {
httpRequest({
url: httpRequestServer + "/404",
url: httpRequestServer+"/404",
}).then(function(){
fail("should not succeed");
done();
@@ -138,10 +138,10 @@ describe("httpRequest", () => {
})
it("should post on echo", (done) => {
let calls = 0;
var calls = 0;
httpRequest({
method: "POST",
url: httpRequestServer + "/echo",
url: httpRequestServer+"/echo",
body: {
foo: "bar"
},
@@ -218,7 +218,7 @@ describe("httpRequest", () => {
it("should params object to query string", (done) => {
httpRequest({
url: httpRequestServer + "/qs",
url: httpRequestServer+"/qs",
params: {
foo: "bar"
}
@@ -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);
@@ -307,7 +307,7 @@ describe("httpRequest", () => {
const httpResponse = new HTTPResponse({}, new Buffer(json));
const encoded = Parse._encode(httpResponse);
let foundData, foundText, foundBody = false;
for(const key in encoded) {
for(var key in encoded) {
if (key == 'data') {
foundData = true;
}
+12 -12
View File
@@ -2,16 +2,16 @@ const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
describe('InMemoryCache', function() {
const BASE_TTL = {
ttl: 100
var BASE_TTL = {
ttl: 10
};
const NO_EXPIRE_TTL = {
var NO_EXPIRE_TTL = {
ttl: NaN
};
const KEY = 'hello';
const KEY_2 = KEY + '_2';
var KEY = 'hello';
var KEY_2 = KEY + '_2';
const VALUE = 'world';
var VALUE = 'world';
function wait(sleep) {
@@ -21,14 +21,14 @@ describe('InMemoryCache', function() {
}
it('should destroy a expire items in the cache', (done) => {
const cache = new InMemoryCache(BASE_TTL);
var cache = new InMemoryCache(BASE_TTL);
cache.put(KEY, VALUE);
let value = cache.get(KEY);
var value = cache.get(KEY);
expect(value).toEqual(VALUE);
wait(BASE_TTL.ttl * 10).then(() => {
wait(BASE_TTL.ttl * 5).then(() => {
value = cache.get(KEY)
expect(value).toEqual(null);
done();
@@ -36,7 +36,7 @@ describe('InMemoryCache', function() {
});
it('should delete items', (done) => {
const cache = new InMemoryCache(NO_EXPIRE_TTL);
var cache = new InMemoryCache(NO_EXPIRE_TTL);
cache.put(KEY, VALUE);
cache.put(KEY_2, VALUE);
expect(cache.get(KEY)).toEqual(VALUE);
@@ -53,7 +53,7 @@ describe('InMemoryCache', function() {
});
it('should clear all items', (done) => {
const cache = new InMemoryCache(NO_EXPIRE_TTL);
var cache = new InMemoryCache(NO_EXPIRE_TTL);
cache.put(KEY, VALUE);
cache.put(KEY_2, VALUE);
@@ -67,7 +67,7 @@ describe('InMemoryCache', function() {
});
it('should deafult TTL to 5 seconds', () => {
const cache = new InMemoryCache({});
var cache = new InMemoryCache({});
expect(cache.ttl).toEqual(5 * 1000);
});
+6 -6
View File
@@ -1,8 +1,8 @@
const InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
describe('InMemoryCacheAdapter', function() {
const KEY = 'hello';
const VALUE = 'world';
var KEY = 'hello';
var VALUE = 'world';
function wait(sleep) {
return new Promise(function(resolve) {
@@ -11,7 +11,7 @@ describe('InMemoryCacheAdapter', function() {
}
it('should expose promisifyed methods', (done) => {
const cache = new InMemoryCacheAdapter({
var cache = new InMemoryCacheAdapter({
ttl: NaN
});
@@ -27,7 +27,7 @@ describe('InMemoryCacheAdapter', function() {
});
it('should get/set/clear', (done) => {
const cache = new InMemoryCacheAdapter({
var cache = new InMemoryCacheAdapter({
ttl: NaN
});
@@ -41,7 +41,7 @@ describe('InMemoryCacheAdapter', function() {
});
it('should expire after ttl', (done) => {
const cache = new InMemoryCacheAdapter({
var cache = new InMemoryCacheAdapter({
ttl: 10
});
+91 -91
View File
@@ -1,20 +1,20 @@
const auth = require('../src/Auth');
const Config = require('../src/Config');
const rest = require('../src/rest');
const InstallationsRouter = require('../src/Routers/InstallationsRouter').InstallationsRouter;
var auth = require('../src/Auth');
var Config = require('../src/Config');
var rest = require('../src/rest');
var InstallationsRouter = require('../src/Routers/InstallationsRouter').InstallationsRouter;
describe('InstallationsRouter', () => {
it('uses find condition from request.body', (done) => {
const config = Config.get('test');
const androidDeviceRequest = {
var config = new Config('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
};
const iosDeviceRequest = {
var iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
};
const request = {
var request = {
config: config,
auth: auth.master(config),
body: {
@@ -26,33 +26,33 @@ describe('InstallationsRouter', () => {
info: {}
};
const router = new 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) => {
const 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) => {
const config = Config.get('test');
const androidDeviceRequest = {
var config = new Config('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
};
const iosDeviceRequest = {
var iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
};
const request = {
var request = {
config: config,
auth: auth.master(config),
body: {},
@@ -64,33 +64,33 @@ describe('InstallationsRouter', () => {
info: {}
};
const router = new 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) => {
const 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) => {
const config = Config.get('test');
const androidDeviceRequest = {
var config = new Config('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
};
const iosDeviceRequest = {
var iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
};
const request = {
var request = {
config: config,
auth: auth.master(config),
body: {},
@@ -100,34 +100,34 @@ describe('InstallationsRouter', () => {
info: {}
};
Config.get('test');
const router = new InstallationsRouter();
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) => {
const 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 => {
const config = Config.get('test');
const androidDeviceRequest = {
var config = new Config('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
};
const iosDeviceRequest = {
var iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
};
const request = {
var request = {
config: config,
auth: auth.master(config),
body: {},
@@ -137,33 +137,33 @@ describe('InstallationsRouter', () => {
info: {}
};
const router = new 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) => {
const 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) => {
const config = Config.get('test');
const androidDeviceRequest = {
var config = new Config('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
};
const iosDeviceRequest = {
var iosDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
'deviceType': 'ios'
};
const request = {
var request = {
config: config,
auth: auth.master(config),
body: {},
@@ -174,20 +174,20 @@ describe('InstallationsRouter', () => {
info: {}
};
const router = new 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) => {
const 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
@@ -1,198 +0,0 @@
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);
});
});
+4 -4
View File
@@ -1,5 +1,5 @@
const logging = require('../src/Adapters/Logger/WinstonLogger');
const winston = require('winston');
var logging = require('../src/Adapters/Logger/WinstonLogger');
var winston = require('winston');
class TestTransport extends winston.Transport {
log(level, msg, meta, callback) {
@@ -51,8 +51,8 @@ describe('Logger', () => {
spyOn(process.stdout, 'write');
logging.logger.info('hi', {key: 'value'});
expect(process.stdout.write).toHaveBeenCalled();
const firstLog = process.stdout.write.calls.first().args[0];
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' }) + '\n');
var firstLog = process.stdout.write.calls.first().args[0];
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' })+'\n');
return reconfigureServer({
jsonLogs: false
});
+9 -33
View File
@@ -1,12 +1,12 @@
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
describe('LoggerController', () => {
it('can check process a query without throwing', (done) => {
// Make mock request
const query = {};
var query = {};
const loggerController = new LoggerController(new WinstonLoggerAdapter());
var loggerController = new LoggerController(new WinstonLoggerAdapter());
expect(() => {
loggerController.getLogs(query).then(function(res) {
@@ -29,7 +29,7 @@ describe('LoggerController', () => {
it('can set the proper default values', (done) => {
// Make mock request
const result = LoggerController.parseOptions();
var result = LoggerController.parseOptions();
expect(result.size).toEqual(10);
expect(result.order).toEqual('desc');
expect(result.level).toEqual('info');
@@ -39,7 +39,7 @@ describe('LoggerController', () => {
it('can process a query without throwing', (done) => {
// Make mock request
const query = {
var query = {
from: "2016-01-01Z00:00:00",
until: "2016-01-01Z00:00:00",
size: 5,
@@ -47,7 +47,7 @@ describe('LoggerController', () => {
level: 'error'
};
const result = LoggerController.parseOptions(query);
var result = LoggerController.parseOptions(query);
expect(result.from.getTime()).toEqual(1451606400000);
expect(result.until.getTime()).toEqual(1451606400000);
@@ -60,7 +60,7 @@ describe('LoggerController', () => {
it('can check process a query without throwing', (done) => {
// Make mock request
const query = {
var query = {
from: "2016-01-01",
until: "2016-01-30",
size: 5,
@@ -68,7 +68,7 @@ describe('LoggerController', () => {
level: 'error'
};
const loggerController = new LoggerController(new WinstonLoggerAdapter());
var loggerController = new LoggerController(new WinstonLoggerAdapter());
expect(() => {
loggerController.getLogs(query).then(function(res) {
@@ -88,28 +88,4 @@ describe('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();
});
});
+7 -96
View File
@@ -1,16 +1,16 @@
'use strict';
const request = require('request');
const LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
const loggerController = new LoggerController(new WinstonLoggerAdapter());
describe('LogsRouter', () => {
it('can check valid master key of request', (done) => {
// Make mock request
const request = {
var request = {
auth: {
isMaster: true
},
@@ -20,7 +20,7 @@ describe('LogsRouter', () => {
}
};
const router = new LogsRouter();
var router = new LogsRouter();
expect(() => {
router.validateRequest(request);
@@ -30,7 +30,7 @@ describe('LogsRouter', () => {
it('can check invalid construction of controller', (done) => {
// Make mock request
const request = {
var request = {
auth: {
isMaster: true
},
@@ -40,7 +40,7 @@ describe('LogsRouter', () => {
}
};
const router = new LogsRouter();
var router = new LogsRouter();
expect(() => {
router.validateRequest(request);
@@ -62,93 +62,4 @@ describe('LogsRouter', () => {
done();
});
});
const headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Master-Key': 'test'
};
/**
* Verifies simple passwords in GET login requests with special characters are scrubbed from the verbose log
*/
it('does scrub simple passwords on GET login', done => {
reconfigureServer({
verbose: true
}).then(function() {
request.get({
headers: headers,
url: 'http://localhost:8378/1/login?username=test&password=simplepass.com'
}, () => {
request.get({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
json: true,
headers: headers
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
// 4th entry is our actual GET request
expect(body[3].url).toEqual('/1/login?username=test&password=********');
expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}');
done();
});
});
});
});
/**
* Verifies complex passwords in GET login requests with special characters are scrubbed from the verbose log
*/
it('does scrub complex passwords on GET login', done => {
reconfigureServer({
verbose: true
}).then(function() {
request.get({
headers: headers,
// using urlencoded password, 'simple @,/?:&=+$#pass.com'
url: 'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com'
}, () => {
request.get({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
json: true,
headers: headers
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
// 4th entry is our actual GET request
expect(body[3].url).toEqual('/1/login?username=test&password=********');
expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}');
done();
});
});
});
});
/**
* Verifies fields in POST login requests are NOT present in the verbose log
*/
it('does not have password field in POST login', done => {
reconfigureServer({
verbose: true
}).then(function() {
request.post({
headers: headers,
url: 'http://localhost:8378/1/login',
data: {
username: 'test',
password: 'simplepass.com'
}
}, () => {
request.get({
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
json: true,
headers: headers
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
// 4th entry is our actual GET request
expect(body[3].url).toEqual('/1/login');
expect(body[3].message).toEqual('REQUEST for [POST] /1/login: {}');
done();
});
});
});
});
});
+5 -175
View File
@@ -1,9 +1,9 @@
const middlewares = require('../src/middlewares');
const AppCache = require('../src/cache').AppCache;
var middlewares = require('../src/middlewares');
var AppCache = require('../src/cache').AppCache;
describe('middlewares', () => {
let fakeReq, fakeRes;
var fakeReq, fakeRes;
beforeEach(() => {
fakeReq = {
@@ -27,7 +27,7 @@ describe('middlewares', () => {
it('should use _ContentType if provided', (done) => {
expect(fakeReq.headers['content-type']).toEqual(undefined);
const contentType = 'image/jpeg';
var contentType = 'image/jpeg';
fakeReq.body._ContentType = contentType;
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeReq.headers['content-type']).toEqual(contentType);
@@ -79,19 +79,6 @@ describe('middlewares', () => {
});
});
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'
@@ -115,7 +102,7 @@ describe('middlewares', () => {
BodyKeys.forEach((infoKey) => {
const bodyKey = BodyParams[infoKey];
const keyValue = 'Fake' + bodyKey;
// javascriptKey is the only one that gets defaulted,
// 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) => {
@@ -133,161 +120,4 @@ describe('middlewares', () => {
});
});
});
it('should not succeed if the ip does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1','ip2']
});
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);
});
});
-9
View File
@@ -1,9 +0,0 @@
module.exports = function(options) {
return {
options: options,
send: function() {},
getValidPushTypes: function() {
return Object.keys(options.options);
}
};
};
+1 -7
View File
@@ -21,9 +21,6 @@ describe('MongoSchemaCollection', () => {
"create":{"*":true},
"delete":{"*":true},
"addField":{"*":true},
},
"indexes": {
"name1":{"deviceToken":1}
}
},
"installationId":"string",
@@ -69,10 +66,7 @@ describe('MongoSchemaCollection', () => {
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
},
indexes: {
name1: {deviceToken: 1}
},
}
});
done();
});
+117 -136
View File
@@ -1,7 +1,7 @@
'use strict';
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
const { MongoClient } = require('mongodb');
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
const MongoClient = require('mongodb').MongoClient;
const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
// These tests are specific to the mongo storage adapter + mongo storage format
@@ -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', () => {
@@ -35,7 +35,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
);
});
// https://github.com/parse-community/parse-server/pull/148#issuecomment-180407057
// https://github.com/ParsePlatform/parse-server/pull/148#issuecomment-180407057
it('preserves replica sets', () => {
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
new MongoStorageAdapter({
@@ -50,14 +50,14 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
it('stores objectId in _id', done => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', {}))
.then(results => {
expect(results.length).toEqual(1);
const 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) => {
@@ -67,13 +67,13 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
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}`);
}
);
.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) => {
@@ -83,18 +83,18 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
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();
}
);
.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) => {
@@ -111,16 +111,16 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
objectId: { type: 'String' },
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
}}, obj)
.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();
});
.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 => {
@@ -128,25 +128,25 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
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);
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();
});
.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 creating an array, object, date', (done) => {
@@ -165,29 +165,29 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
date: { type: 'Date' },
} };
adapter.createObject('MyClass', schema, obj)
.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();
});
.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) => {
@@ -201,58 +201,39 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
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();
});
});
it('handleShutdown, close connection', (done) => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
const schema = {
fields: {
array: { type: 'Array' },
object: { type: 'Object' },
date: { type: 'Date' },
}
};
adapter.createObject('MyClass', schema, {}).then(() => {
expect(adapter.database.serverConfig.isConnected()).toEqual(true);
adapter.handleShutdown()
expect(adapter.database.serverConfig.isConnected()).toEqual(false);
.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();
});
});
+55 -275
View File
@@ -7,8 +7,8 @@ const mongodb = require('mongodb');
describe('parseObjectToMongoObjectForCreate', () => {
it('a basic number', (done) => {
const input = {five: 5};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
var input = {five: 5};
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {five: {type: 'Number'}}
});
jequal(input, output);
@@ -16,8 +16,8 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('an object with null values', (done) => {
const input = {objectWithNullValues: {isNull: null, notNull: 3}};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
var input = {objectWithNullValues: {isNull: null, notNull: 3}};
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {objectWithNullValues: {type: 'object'}}
});
jequal(input, output);
@@ -25,23 +25,23 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('built-in timestamps', (done) => {
const input = {
var input = {
createdAt: "2015-10-06T21:24:50.332Z",
updatedAt: "2015-10-06T21:24:50.332Z"
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
var output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
expect(output._created_at instanceof Date).toBe(true);
expect(output._updated_at instanceof Date).toBe(true);
done();
});
it('array of pointers', (done) => {
const pointer = {
var pointer = {
__type: 'Pointer',
objectId: 'myId',
className: 'Blah',
};
const out = transform.parseObjectToMongoObjectForCreate(null, {pointers: [pointer]},{
var out = transform.parseObjectToMongoObjectForCreate(null, {pointers: [pointer]},{
fields: {pointers: {type: 'Array'}}
});
jequal([pointer], out.pointers);
@@ -51,47 +51,30 @@ describe('parseObjectToMongoObjectForCreate', () => {
//TODO: object creation requests shouldn't be seeing __op delete, it makes no sense to
//have __op delete in a new object. Figure out what this should actually be testing.
xit('a delete op', (done) => {
const input = {deleteMe: {__op: 'Delete'}};
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
var input = {deleteMe: {__op: 'Delete'}};
var output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
jequal(output, {});
done();
});
it('Doesnt allow ACL, as Parse Server should tranform ACL to _wperm + _rperm', done => {
const input = {ACL: {'0123': {'read': true, 'write': true}}};
var input = {ACL: {'0123': {'read': true, 'write': true}}};
expect(() => transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} })).toThrow();
done();
});
it('parse geopoint to mongo', (done) => {
const lat = -45;
const lng = 45;
const geoPoint = {__type: 'GeoPoint', latitude: lat, longitude: lng};
const out = transform.parseObjectToMongoObjectForCreate(null, {location: geoPoint},{
it('plain', (done) => {
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
var out = transform.parseObjectToMongoObjectForCreate(null, {location: geoPoint},{
fields: {location: {type: 'GeoPoint'}}
});
expect(out.location).toEqual([lng, lat]);
done();
});
it('parse polygon to mongo', (done) => {
const lat1 = -45;
const lng1 = 45;
const lat2 = -55;
const lng2 = 55;
const lat3 = -65;
const lng3 = 65;
const polygon = {__type: 'Polygon', coordinates: [[lat1, lng1],[lat2, lng2],[lat3, lng3]]}
const out = transform.parseObjectToMongoObjectForCreate(null, {location: polygon},{
fields: {location: {type: 'Polygon'}}
});
expect(out.location.coordinates).toEqual([[[lng1, lat1],[lng2, lat2],[lng3, lat3],[lng1, lat1]]]);
expect(out.location).toEqual([180, -180]);
done();
});
it('in array', (done) => {
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
const out = transform.parseObjectToMongoObjectForCreate(null, {locations: [geoPoint, geoPoint]},{
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
var out = transform.parseObjectToMongoObjectForCreate(null, {locations: [geoPoint, geoPoint]},{
fields: {locations: {type: 'Array'}}
});
expect(out.locations).toEqual([geoPoint, geoPoint]);
@@ -99,8 +82,8 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('in sub-object', (done) => {
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
const out = transform.parseObjectToMongoObjectForCreate(null, { locations: { start: geoPoint }},{
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
var out = transform.parseObjectToMongoObjectForCreate(null, { locations: { start: geoPoint }},{
fields: {locations: {type: 'Object'}}
});
expect(out).toEqual({ locations: { start: geoPoint } });
@@ -108,31 +91,31 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('objectId', (done) => {
const out = transform.transformWhere(null, {objectId: 'foo'});
var out = transform.transformWhere(null, {objectId: 'foo'});
expect(out._id).toEqual('foo');
done();
});
it('objectId in a list', (done) => {
const input = {
var input = {
objectId: {'$in': ['one', 'two', 'three']},
};
const output = transform.transformWhere(null, input);
var output = transform.transformWhere(null, input);
jequal(input.objectId, output._id);
done();
});
it('built-in timestamps', (done) => {
const input = {createdAt: new Date(), updatedAt: new Date()};
const output = transform.mongoObjectToParseObject(null, input, { fields: {} });
var input = {createdAt: new Date(), updatedAt: new Date()};
var output = transform.mongoObjectToParseObject(null, input, { fields: {} });
expect(typeof output.createdAt).toEqual('string');
expect(typeof output.updatedAt).toEqual('string');
done();
});
it('pointer', (done) => {
const input = {_p_userPointer: '_User$123'};
const output = transform.mongoObjectToParseObject(null, input, {
var input = {_p_userPointer: '_User$123'};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
});
expect(typeof output.userPointer).toEqual('object');
@@ -143,8 +126,8 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('null pointer', (done) => {
const input = {_p_userPointer: null};
const output = transform.mongoObjectToParseObject(null, input, {
var input = {_p_userPointer: null};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
});
expect(output.userPointer).toBeUndefined();
@@ -152,8 +135,8 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('file', (done) => {
const input = {picture: 'pic.jpg'};
const output = transform.mongoObjectToParseObject(null, input, {
var input = {picture: 'pic.jpg'};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { picture: { type: 'File' }},
});
expect(typeof output.picture).toEqual('object');
@@ -161,38 +144,21 @@ describe('parseObjectToMongoObjectForCreate', () => {
done();
});
it('mongo geopoint to parse', (done) => {
const lat = -45;
const lng = 45;
const input = {location: [lng, lat]};
const output = transform.mongoObjectToParseObject(null, input, {
it('geopoint', (done) => {
var input = {location: [180, -180]};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'GeoPoint' }},
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', latitude: lat, longitude: lng}
);
done();
});
it('mongo polygon to parse', (done) => {
const lat = -45;
const lng = 45;
// Mongo stores polygon in WGS84 lng/lat
const input = {location: { type: 'Polygon', coordinates: [[[lat, lng],[lat, lng]]]}};
const output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'Polygon' }},
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'Polygon', coordinates: [[lng, lat],[lng, lat]]}
{__type: 'GeoPoint', longitude: 180, latitude: -180}
);
done();
});
it('bytes', (done) => {
const input = {binaryData: "aGVsbG8gd29ybGQ="};
const output = transform.mongoObjectToParseObject(null, input, {
var input = {binaryData: "aGVsbG8gd29ybGQ="};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { binaryData: { type: 'Bytes' }},
});
expect(typeof output.binaryData).toEqual('object');
@@ -203,8 +169,8 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('nested array', (done) => {
const input = {arr: [{_testKey: 'testValue' }]};
const output = transform.mongoObjectToParseObject(null, input, {
var input = {arr: [{_testKey: 'testValue' }]};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { arr: { type: 'Array' } },
});
expect(Array.isArray(output.arr)).toEqual(true);
@@ -232,10 +198,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('changes new pointer key', (done) => {
const input = {
var input = {
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {somePointer: {type: 'Pointer'}}
});
expect(typeof output._p_somePointer).toEqual('string');
@@ -244,10 +210,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('changes existing pointer keys', (done) => {
const input = {
var input = {
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {userPointer: {type: 'Pointer'}}
});
expect(typeof output._p_userPointer).toEqual('string');
@@ -256,12 +222,12 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('writes the old ACL format in addition to rperm and wperm on create', (done) => {
const input = {
var input = {
_rperm: ['*'],
_wperm: ['Kevin'],
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
var output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
expect(typeof output._acl).toEqual('object');
expect(output._acl['Kevin'].w).toBeTruthy();
expect(output._acl['Kevin'].r).toBeUndefined();
@@ -271,10 +237,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('removes Relation types', (done) => {
const input = {
var input = {
aRelation: { __type: 'Relation', className: 'Stuff' },
};
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
fields: {
aRelation: { __type: 'Relation', className: 'Stuff' },
},
@@ -284,13 +250,13 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
const input = {
var input = {
_rperm: ['*'],
_wperm: ['Kevin']
};
const output = transform.transformUpdate(null, input, { fields: {} });
const set = output.$set;
var output = transform.transformUpdate(null, input, { fields: {} });
var set = output.$set;
expect(typeof set).toEqual('object');
expect(typeof set._acl).toEqual('object');
expect(set._acl['Kevin'].w).toBeTruthy();
@@ -301,11 +267,11 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('untransforms from _rperm and _wperm to ACL', (done) => {
const input = {
var input = {
_rperm: ["*"],
_wperm: ["Kevin"]
};
const output = transform.mongoObjectToParseObject(null, input, { fields: {} });
var output = transform.mongoObjectToParseObject(null, input, { fields: {} });
expect(output._rperm).toEqual(['*']);
expect(output._wperm).toEqual(['Kevin']);
expect(output.ACL).toBeUndefined()
@@ -313,11 +279,11 @@ describe('parseObjectToMongoObjectForCreate', () => {
});
it('untransforms mongodb number types', (done) => {
const input = {
var input = {
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
double: new mongodb.Double(Number.MAX_VALUE)
}
const output = transform.mongoObjectToParseObject(null, input, {
var output = transform.mongoObjectToParseObject(null, input, {
fields: {
long: { type: 'Number' },
double: { type: 'Number' },
@@ -327,75 +293,14 @@ describe('parseObjectToMongoObjectForCreate', () => {
expect(output.double).toBe(Number.MAX_VALUE);
done();
});
it('Date object where iso attribute is of type Date', (done) => {
const input = {
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
}
const 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) => {
const input = {
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
}
const output = transform.mongoObjectToParseObject(null, input, {
fields : {
ts : { type : 'Date' }
}
});
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
done();
});
it('$regex in $all list', (done) => {
const input = {
arrayField: {'$all': [{$regex: '^\\Qone\\E'}, {$regex: '^\\Qtwo\\E'}, {$regex: '^\\Qthree\\E'}]},
};
const outputValue = {
arrayField: {'$all': [/^\Qone\E/, /^\Qtwo\E/, /^\Qthree\E/]},
};
const output = transform.transformWhere(null, input);
jequal(outputValue.arrayField, output.arrayField);
done();
});
it('$regex in $all list must be { $regex: "string" }', (done) => {
const input = {
arrayField: {'$all': [{$regex: 1}]},
};
expect(() => {
transform.transformWhere(null, input)
}).toThrow();
done();
});
it('all values in $all must be $regex (start with string) or non $regex (start with string)', (done) => {
const input = {
arrayField: {'$all': [{$regex: '^\\Qone\\E'}, {$unknown: '^\\Qtwo\\E'}]},
};
expect(() => {
transform.transformWhere(null, input)
}).toThrow();
done();
});
});
describe('transformUpdate', () => {
it('removes Relation types', (done) => {
const input = {
var input = {
aRelation: { __type: 'Relation', className: 'Stuff' },
};
const output = transform.transformUpdate(null, input, {
var output = transform.transformUpdate(null, input, {
fields: {
aRelation: { __type: 'Relation', className: 'Stuff' },
},
@@ -404,128 +309,3 @@ describe('transformUpdate', () => {
done();
});
});
describe('transformConstraint', () => {
describe('$relativeTime', () => {
it('should error on $eq, $ne, and $exists', () => {
expect(() => {
transform.transformConstraint({
$eq: {
ttl: {
$relativeTime: '12 days ago',
}
}
});
}).toThrow();
expect(() => {
transform.transformConstraint({
$ne: {
ttl: {
$relativeTime: '12 days ago',
}
}
});
}).toThrow();
expect(() => {
transform.transformConstraint({
$exists: {
$relativeTime: '12 days ago',
}
});
}).toThrow();
});
})
});
describe('relativeTimeToDate', () => {
const now = new Date('2017-09-26T13:28:16.617Z');
describe('In the future', () => {
it('should parse valid natural time', () => {
const text = 'in 1 year 2 weeks 12 days 10 hours 24 minutes 30 seconds';
const { result, status, info } = transform.relativeTimeToDate(text, now);
expect(result.toISOString()).toBe('2018-10-22T23:52:46.617Z');
expect(status).toBe('success');
expect(info).toBe('future');
});
});
describe('In the past', () => {
it('should parse valid natural time', () => {
const text = '2 days 12 hours 1 minute 12 seconds ago';
const { result, status, info } = transform.relativeTimeToDate(text, now);
expect(result.toISOString()).toBe('2017-09-24T01:27:04.617Z');
expect(status).toBe('success');
expect(info).toBe('past');
});
});
describe('From now', () => {
it('should equal current time', () => {
const text = 'now';
const { result, status, info } = transform.relativeTimeToDate(text, now);
expect(result.toISOString()).toBe('2017-09-26T13:28:16.617Z');
expect(status).toBe('success');
expect(info).toBe('present');
});
});
describe('Error cases', () => {
it('should error if string is completely gibberish', () => {
expect(transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({
status: 'error',
info: "Time should either start with 'in' or end with 'ago'",
});
});
it('should error if string contains neither `ago` nor `in`', () => {
expect(transform.relativeTimeToDate('12 hours 1 minute')).toEqual({
status: 'error',
info: "Time should either start with 'in' or end with 'ago'",
});
});
it('should error if there are missing units or numbers', () => {
expect(transform.relativeTimeToDate('in 12 hours 1')).toEqual({
status: 'error',
info: 'Invalid time string. Dangling unit or number.',
});
expect(transform.relativeTimeToDate('12 hours minute ago')).toEqual({
status: 'error',
info: 'Invalid time string. Dangling unit or number.',
});
});
it('should error on floating point numbers', () => {
expect(transform.relativeTimeToDate('in 12.3 hours')).toEqual({
status: 'error',
info: "'12.3' is not an integer.",
});
});
it('should error if numbers are invalid', () => {
expect(transform.relativeTimeToDate('12 hours 123a minute ago')).toEqual({
status: 'error',
info: "'123a' is not an integer.",
});
});
it('should error on invalid interval units', () => {
expect(transform.relativeTimeToDate('4 score 7 years ago')).toEqual({
status: 'error',
info: "Invalid interval: 'score'",
});
});
it("should error when string contains 'ago' and 'in'", () => {
expect(transform.relativeTimeToDate('in 1 day 2 minutes ago')).toEqual({
status: 'error',
info: "Time cannot have both 'in' and 'ago'",
});
});
});
});
+5 -5
View File
@@ -1,11 +1,11 @@
const NullCacheAdapter = require('../src/Adapters/Cache/NullCacheAdapter').default;
var NullCacheAdapter = require('../src/Adapters/Cache/NullCacheAdapter').default;
describe('NullCacheAdapter', function() {
const KEY = 'hello';
const VALUE = 'world';
var KEY = 'hello';
var VALUE = 'world';
it('should expose promisifyed methods', (done) => {
const cache = new NullCacheAdapter({
var cache = new NullCacheAdapter({
ttl: NaN
});
@@ -21,7 +21,7 @@ describe('NullCacheAdapter', function() {
});
it('should get/set/clear', (done) => {
const cache = new NullCacheAdapter({
var cache = new NullCacheAdapter({
ttl: NaN
});
+27 -37
View File
@@ -1,4 +1,4 @@
const OAuth = require("../src/Adapters/Auth/OAuth1Client");
var OAuth = require("../src/Adapters/Auth/OAuth1Client");
describe('OAuth', function() {
it("Nonce should have right length", (done) => {
@@ -7,45 +7,45 @@ describe('OAuth', function() {
});
it("Should properly build parameter string", (done) => {
const string = OAuth.buildParameterString({c:1, a:2, b:3})
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) => {
const string = OAuth.buildParameterString()
var string = OAuth.buildParameterString()
jequal(string, "");
done();
});
it("Should properly build signature string", (done) => {
const string = OAuth.buildSignatureString("get", "http://dummy.com", "");
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
done();
});
it("Should properly generate request signature", (done) => {
let request = {
var request = {
host: "dummy.com",
path: "path"
};
const oauth_params = {
var oauth_params = {
oauth_timestamp: 123450000,
oauth_nonce: "AAAAAAAAAAAAAAAAA",
oauth_consumer_key: "hello",
oauth_token: "token"
};
const consumer_secret = "world";
const auth_token_secret = "secret";
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) => {
const options = {
var options = {
host: "dummy.com",
consumer_key: "hello",
consumer_secret: "world",
@@ -57,14 +57,14 @@ describe('OAuth', function() {
oauth_nonce: "AAAAAAAAAAAAAAAAA"
}
};
const path = "path";
const method = "get";
var path = "path";
var method = "get";
const oauthClient = new OAuth(options);
const req = oauthClient.buildRequest(method, path, {"query": "param"});
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.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"')
@@ -75,7 +75,7 @@ describe('OAuth', function() {
function validateCannotAuthenticateError(data, done) {
jequal(typeof data, "object");
jequal(typeof data.errors, "object");
const errors = data.errors;
var errors = data.errors;
jequal(typeof errors[0], "object");
// Cannot authenticate error
jequal(errors[0].code, 32);
@@ -83,48 +83,48 @@ describe('OAuth', function() {
}
it("Should fail a GET request", (done) => {
const options = {
var options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
const path = "/1.1/help/configuration.json";
const params = {"lang": "en"};
const oauthClient = new OAuth(options);
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) => {
const options = {
var options = {
host: "api.twitter.com",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
const body = {
var body = {
lang: "en"
};
const path = "/1.1/account/settings.json";
var path = "/1.1/account/settings.json";
const oauthClient = new OAuth(options);
var oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(data){
validateCannotAuthenticateError(data, done);
})
});
it("Should fail a request", (done) => {
const options = {
var options = {
host: "localhost",
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
};
const body = {
var body = {
lang: "en"
};
const path = "/";
var path = "/";
const oauthClient = new OAuth(options);
var oauthClient = new OAuth(options);
oauthClient.post(path, null, body).then(function(){
jequal(false, true);
done();
@@ -133,14 +133,4 @@ describe('OAuth', function() {
done();
})
});
it("Should fail with missing options", (done) => {
const options = undefined;
try {
new OAuth(options);
} catch (error) {
jequal(error.message, 'No options passed to OAuth');
done();
}
});
});
+88 -372
View File
@@ -2,32 +2,22 @@
const request = require('request');
const delayPromise = (delay) => {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
describe('Parse.Push', () => {
const setup = function() {
const sendToInstallationSpy = jasmine.createSpy();
const pushAdapter = {
var setup = function() {
var pushAdapter = {
send: function(body, installations) {
const badge = body.data.badge;
var badge = body.data.badge;
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,
device: installation,
transmitted: true
deviceType: installation.deviceType,
result: true
})
});
return Promise.all(promises);
@@ -45,33 +35,25 @@ describe('Parse.Push', () => {
adapter: pushAdapter
}
})
.then(() => {
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
return Parse.Object.saveAll(installations);
})
.then(() => {
return {
sendToInstallationSpy,
};
})
.catch((err) => {
console.error(err);
throw 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);
}).catch((err) => {
console.error(err);
})
}
it('should properly send push', (done) => {
return setup().then(({ sendToInstallationSpy }) => {
return setup().then(() => {
return Parse.Push.send({
where: {
deviceType: 'ios'
@@ -81,13 +63,8 @@ describe('Parse.Push', () => {
alert: 'Hello world!'
}
}, {useMasterKey: true})
.then(() => {
return delayPromise(500);
})
.then(() => {
expect(sendToInstallationSpy.calls.count()).toEqual(10);
})
}).then(() => {
})
.then(() => {
done();
}).catch((err) => {
jfail(err);
@@ -106,8 +83,6 @@ describe('Parse.Push', () => {
alert: 'Hello world!'
}
}, {useMasterKey: true})
}).then(() => {
return delayPromise(500);
}).then(() => {
done();
}).catch((err) => {
@@ -118,36 +93,71 @@ describe('Parse.Push', () => {
it('should not allow clients to query _PushStatus', done => {
setup()
.then(() => Parse.Push.send({
where: {
deviceType: 'ios'
.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',
},
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);
}, (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({
.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 Parse.Push.send({
where: {
deviceType: 'ios'
},
@@ -155,308 +165,14 @@ describe('Parse.Push', () => {
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();
});
});
const successfulAny = function(body, installations) {
const promises = installations.map((device) => {
return Promise.resolve({
transmitted: true,
device: device,
})
});
return Promise.all(promises);
};
const provideInstallations = function(num) {
if(!num) {
num = 2;
}
const installations = [];
while(installations.length !== num) {
// add Android installations
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length);
installation.set("deviceType", "android");
installations.push(installation);
}
return installations;
};
const losingAdapter = {
send: function(body, installations) {
// simulate having lost an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.pop();
return successfulAny(body, installations);
},
getValidPushTypes: function() {
return ["android"];
}
};
/**
* Verifies that _PushStatus cannot get stuck in a 'running' state
* Simulates a simple push where 1 installation is removed between _PushStatus
* count being set and the pushes being sent
*/
it('does not get stuck with _PushStatus \'running\' on 1 installation lost', (done) => {
reconfigureServer({
push: {adapter: losingAdapter}
}, {useMasterKey: true})
}).then(() => {
return Parse.Object.saveAll(provideInstallations());
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: 'android'}
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(1);
expect(result.get('count')).toEqual(undefined);
fail('should not succeed');
}, (err) => {
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
done();
});
});
/**
* Verifies that _PushStatus cannot get stuck in a 'running' state
* Simulates a simple push where 1 installation is added between _PushStatus
* count being set and the pushes being sent
*/
it('does not get stuck with _PushStatus \'running\' on 1 installation added', (done) => {
const installations = provideInstallations();
// add 1 iOS installation which we will omit & add later on
const iOSInstallation = new Parse.Object("_Installation");
iOSInstallation.set("installationId", "installation_" + installations.length);
iOSInstallation.set("deviceToken","device_token_" + installations.length);
iOSInstallation.set("deviceType", "ios");
installations.push(iOSInstallation);
reconfigureServer({
push: {
adapter: {
send: function(body, installations) {
// simulate having added an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.push(iOSInstallation);
return successfulAny(body, installations);
},
getValidPushTypes: function() {
return ["android"];
}
}
}
}).then(() => {
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: {'$ne' : 'random'}}
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(3);
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
* Verifies that _PushStatus cannot get stuck in a 'running' state
* Simulates an extended push, where some installations may be removed,
* resulting in a non-zero count
*/
it('does not get stuck with _PushStatus \'running\' on many installations removed', (done) => {
const devices = 1000;
const installations = provideInstallations(devices);
reconfigureServer({
push: {adapter: losingAdapter}
}).then(() => {
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: 'android'}
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices - (devices / 100));
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
* Verifies that _PushStatus cannot get stuck in a 'running' state
* Simulates an extended push, where some installations may be added,
* resulting in a non-zero count
*/
it('does not get stuck with _PushStatus \'running\' on many installations added', (done) => {
const devices = 1000;
const installations = provideInstallations(devices);
// add 1 iOS installation which we will omit & add later on
const iOSInstallations = [];
while(iOSInstallations.length !== (devices / 100)) {
const iOSInstallation = new Parse.Object("_Installation");
iOSInstallation.set("installationId", "installation_" + installations.length);
iOSInstallation.set("deviceToken", "device_token_" + installations.length);
iOSInstallation.set("deviceType", "ios");
installations.push(iOSInstallation);
iOSInstallations.push(iOSInstallation);
}
reconfigureServer({
push: {
adapter: {
send: function(body, installations) {
// simulate having added an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.push(iOSInstallations.pop());
return successfulAny(body, installations);
},
getValidPushTypes: function() {
return ["android"];
}
}
}
}).then(() => {
return Parse.Object.saveAll(installations);
}).then(() => {
return Parse.Push.send(
{
data: {alert: "We fixed our status!"},
where: {deviceType: {'$ne' : 'random'}}
},
{ useMasterKey: true }
);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}).then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices + (devices / 100));
expect(result.get('count')).toEqual(undefined);
}).catch((err) => {
jfail(err);
done();
});
});
+509 -507
View File
File diff suppressed because it is too large Load Diff
+254 -259
View File
@@ -2,28 +2,28 @@
// It would probably be better to refactor them into different files.
'use strict';
const request = require('request');
var request = require('request');
const rp = require('request-promise');
const Parse = require("parse/node");
const Config = require('../src/Config');
const SchemaController = require('../src/Controllers/SchemaController');
const TestUtils = require('../src/TestUtils');
var TestUtils = require('../src/TestUtils');
const userSchema = SchemaController.convertSchemaToAdapterSchema({ className: '_User', fields: Object.assign({}, SchemaController.defaultColumns._Default, SchemaController.defaultColumns._User) });
describe_only_db('mongo')('miscellaneous', () => {
it('test rest_create_app', function(done) {
let appId;
var appId;
Parse._request('POST', 'rest_create_app').then((res) => {
expect(typeof res.application_id).toEqual('string');
expect(res.master_key).toEqual('master');
appId = res.application_id;
Parse.initialize(appId, 'unused');
const obj = new Parse.Object('TestObject');
var obj = new Parse.Object('TestObject');
obj.set('foo', 'bar');
return obj.save();
}).then(() => {
const config = Config.get(appId);
const config = new Config(appId);
return config.database.adapter.find('TestObject', { fields: {} }, {}, {});
}).then((results) => {
expect(results.length).toEqual(1);
@@ -38,7 +38,7 @@ describe_only_db('mongo')('miscellaneous', () => {
describe('miscellaneous', function() {
it('create a GameScore object', function(done) {
const obj = new Parse.Object('GameScore');
var obj = new Parse.Object('GameScore');
obj.set('score', 1337);
obj.save().then(function(obj) {
expect(typeof obj.id).toBe('string');
@@ -52,7 +52,7 @@ describe('miscellaneous', function() {
it('get a TestObject', function(done) {
create({ 'bloop' : 'blarg' }, function(obj) {
const t2 = new TestObject({ objectId: obj.id });
var t2 = new TestObject({ objectId: obj.id });
t2.fetch({
success: function(obj2) {
expect(obj2.get('bloop')).toEqual('blarg');
@@ -88,27 +88,27 @@ describe('miscellaneous', function() {
numCreated++;
expect(numCreated).toEqual(1);
})
.catch(error => {
numFailed++;
expect(numFailed).toEqual(1);
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
});
.catch(error => {
numFailed++;
expect(numFailed).toEqual(1);
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
});
const p2 = createTestUser();
p2.then(() => {
numCreated++;
expect(numCreated).toEqual(1);
})
.catch(error => {
numFailed++;
expect(numFailed).toEqual(1);
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
});
.catch(error => {
numFailed++;
expect(numFailed).toEqual(1);
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
});
Parse.Promise.when([p1, p2])
.then(() => {
fail('one of the users should not have been created');
done();
})
.catch(done);
.then(() => {
fail('one of the users should not have been created');
done();
})
.catch(done);
});
it('ensure that email is uniquely indexed', done => {
@@ -143,97 +143,97 @@ describe('miscellaneous', function() {
});
Parse.Promise.when([p1, p2])
.then(() => {
fail('one of the users should not have been created');
done();
})
.catch(done);
.then(() => {
fail('one of the users should not have been created');
done();
})
.catch(done);
});
it('ensure that if people already have duplicate users, they can still sign up new users', done => {
const config = Config.get('test');
const config = new Config('test');
// Remove existing data to clear out unique index
TestUtils.destroyAllDataPermanently()
.then(() => config.database.adapter.createClass('_User', userSchema))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'x', username: 'u' }).catch(fail))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'y', username: 'u' }).catch(fail))
.then(() => config.database.adapter.createClass('_User', userSchema))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'x', username: 'u' }).catch(fail))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'y', username: 'u' }).catch(fail))
// Create a new server to try to recreate the unique indexes
.then(reconfigureServer)
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('zxcv');
return user.signUp().catch(fail);
})
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('u');
return user.signUp()
})
.then(() => {
fail('should not have been able to sign up');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
done();
})
.then(reconfigureServer)
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('zxcv');
return user.signUp().catch(fail);
})
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('u');
return user.signUp()
})
.then(() => {
fail('should not have been able to sign up');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
done();
})
});
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
const config = Config.get('test');
const config = new Config('test');
// Remove existing data to clear out unique index
TestUtils.destroyAllDataPermanently()
.then(() => config.database.adapter.createClass('_User', userSchema))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'x', email: 'a@b.c' }))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'y', email: 'a@b.c' }))
.then(reconfigureServer)
.catch(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('qqq');
user.setEmail('unique@unique.unique');
return user.signUp().catch(fail);
})
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('www');
user.setEmail('a@b.c');
return user.signUp()
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
done();
});
.then(() => config.database.adapter.createClass('_User', userSchema))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'x', email: 'a@b.c' }))
.then(() => config.database.adapter.createObject('_User', userSchema, { objectId: 'y', email: 'a@b.c' }))
.then(reconfigureServer)
.catch(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('qqq');
user.setEmail('unique@unique.unique');
return user.signUp().catch(fail);
})
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('www');
user.setEmail('a@b.c');
return user.signUp()
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
done();
});
});
it('ensure that if you try to sign up a user with a unique username and email, but duplicates in some other field that has a uniqueness constraint, you get a regular duplicate value error', done => {
const config = Config.get('test');
const config = new Config('test');
config.database.adapter.addFieldIfNotExists('_User', 'randomField', { type: 'String' })
.then(() => config.database.adapter.ensureUniqueness('_User', userSchema, ['randomField']))
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('1');
user.setEmail('1@b.c');
user.set('randomField', 'a');
return user.signUp()
})
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('2');
user.setEmail('2@b.c');
user.set('randomField', 'a');
return user.signUp()
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
.then(() => config.database.adapter.ensureUniqueness('_User', userSchema, ['randomField']))
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('1');
user.setEmail('1@b.c');
user.set('randomField', 'a');
return user.signUp()
})
.then(() => {
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('2');
user.setEmail('2@b.c');
user.set('randomField', 'a');
return user.signUp()
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
done();
});
});
it('succeed in logging in', function(done) {
@@ -265,24 +265,24 @@ describe('miscellaneous', function() {
user.increment('foo');
return user.save();
}).then(() => Parse.User.logOut())
.then(() => Parse.User.logIn('test', 'moon-y'))
.then((user) => {
expect(user.get('foo')).toEqual(2);
Parse.User.logOut()
.then(done);
}, (error) => {
fail(JSON.stringify(error));
done();
});
.then(() => Parse.User.logIn('test', 'moon-y'))
.then((user) => {
expect(user.get('foo')).toEqual(2);
Parse.User.logOut()
.then(done);
}, (error) => {
fail(JSON.stringify(error));
done();
});
});
it('save various data types', function(done) {
const obj = new TestObject();
var obj = new TestObject();
obj.set('date', new Date());
obj.set('array', [1, 2, 3]);
obj.set('object', {one: 1, two: 2});
obj.save().then(() => {
const obj2 = new TestObject({objectId: obj.id});
var obj2 = new TestObject({objectId: obj.id});
return obj2.fetch();
}).then((obj2) => {
expect(obj2.get('date') instanceof Date).toBe(true);
@@ -294,12 +294,12 @@ describe('miscellaneous', function() {
});
it('query with limit', function(done) {
const baz = new TestObject({ foo: 'baz' });
const qux = new TestObject({ foo: 'qux' });
var baz = new TestObject({ foo: 'baz' });
var qux = new TestObject({ foo: 'qux' });
baz.save().then(() => {
return qux.save();
}).then(() => {
const query = new Parse.Query(TestObject);
var query = new Parse.Query(TestObject);
query.limit(1);
return query.find();
}).then((results) => {
@@ -312,8 +312,8 @@ describe('miscellaneous', function() {
});
it('query without limit get default 100 records', function(done) {
const objects = [];
for (let i = 0; i < 150; i++) {
var objects = [];
for (var i = 0; i < 150; i++) {
objects.push(new TestObject({name: 'name' + i}));
}
Parse.Object.saveAll(objects).then(() => {
@@ -328,8 +328,8 @@ describe('miscellaneous', function() {
});
it('basic saveAll', function(done) {
const alpha = new TestObject({ letter: 'alpha' });
const beta = new TestObject({ letter: 'beta' });
var alpha = new TestObject({ letter: 'alpha' });
var beta = new TestObject({ letter: 'beta' });
Parse.Object.saveAll([alpha, beta]).then(() => {
expect(alpha.id).toBeTruthy();
expect(beta.id).toBeTruthy();
@@ -344,7 +344,7 @@ describe('miscellaneous', function() {
});
it('test beforeSave set object acl success', function(done) {
const acl = new Parse.ACL({
var acl = new Parse.ACL({
'*': { read: true, write: false }
});
Parse.Cloud.beforeSave('BeforeSaveAddACL', function(req, res) {
@@ -352,10 +352,10 @@ describe('miscellaneous', function() {
res.success();
});
const obj = new Parse.Object('BeforeSaveAddACL');
var obj = new Parse.Object('BeforeSaveAddACL');
obj.set('lol', true);
obj.save().then(function() {
const query = new Parse.Query('BeforeSaveAddACL');
var query = new Parse.Query('BeforeSaveAddACL');
query.get(obj.id).then(function(objAgain) {
expect(objAgain.get('lol')).toBeTruthy();
expect(objAgain.getACL().equals(acl));
@@ -534,20 +534,20 @@ describe('miscellaneous', function() {
it('pointer reassign is working properly (#1288)', (done) => {
Parse.Cloud.beforeSave('GameScore', (req, res) => {
const obj = req.object;
var obj = req.object;
if (obj.get('point')) {
return res.success();
}
const TestObject1 = Parse.Object.extend('TestObject1');
const newObj = new TestObject1({'key1': 1});
var TestObject1 = Parse.Object.extend('TestObject1');
var newObj = new TestObject1({'key1': 1});
return newObj.save().then((newObj) => {
obj.set('point' , newObj);
res.success();
});
});
let pointId;
const obj = new Parse.Object('GameScore');
var pointId;
var obj = new Parse.Object('GameScore');
obj.set('foo', 'bar');
obj.save().then(() => {
expect(obj.get('point')).not.toBeUndefined();
@@ -562,10 +562,10 @@ describe('miscellaneous', function() {
});
it('test afterSave get full object on create and update', function(done) {
let triggerTime = 0;
var triggerTime = 0;
// Register a mock beforeSave hook
Parse.Cloud.afterSave('GameScore', function(req, res) {
const object = req.object;
var object = req.object;
expect(object instanceof Parse.Object).toBeTruthy();
expect(object.id).not.toBeUndefined();
expect(object.createdAt).not.toBeUndefined();
@@ -584,7 +584,7 @@ describe('miscellaneous', function() {
res.success();
});
const obj = new Parse.Object('GameScore');
var obj = new Parse.Object('GameScore');
obj.set('foo', 'bar');
obj.set('fooAgain', 'barAgain');
obj.save().then(function() {
@@ -602,17 +602,17 @@ describe('miscellaneous', function() {
});
it('test afterSave get original object on update', function(done) {
let triggerTime = 0;
var triggerTime = 0;
// Register a mock beforeSave hook
Parse.Cloud.afterSave('GameScore', function(req, res) {
const object = req.object;
var object = req.object;
expect(object instanceof Parse.Object).toBeTruthy();
expect(object.get('fooAgain')).toEqual('barAgain');
expect(object.id).not.toBeUndefined();
expect(object.createdAt).not.toBeUndefined();
expect(object.updatedAt).not.toBeUndefined();
const originalObject = req.original;
var originalObject = req.original;
if (triggerTime == 0) {
// Create
expect(object.get('foo')).toEqual('bar');
@@ -635,7 +635,7 @@ describe('miscellaneous', function() {
res.success();
});
const obj = new Parse.Object('GameScore');
var obj = new Parse.Object('GameScore');
obj.set('foo', 'bar');
obj.set('fooAgain', 'barAgain');
obj.save().then(function() {
@@ -653,11 +653,11 @@ describe('miscellaneous', function() {
});
it('test afterSave get full original object even req auth can not query it', (done) => {
let triggerTime = 0;
var triggerTime = 0;
// Register a mock beforeSave hook
Parse.Cloud.afterSave('GameScore', function(req, res) {
const object = req.object;
const originalObject = req.original;
var object = req.object;
var originalObject = req.original;
if (triggerTime == 0) {
// Create
} else if (triggerTime == 1) {
@@ -677,10 +677,10 @@ describe('miscellaneous', function() {
res.success();
});
const obj = new Parse.Object('GameScore');
var obj = new Parse.Object('GameScore');
obj.set('foo', 'bar');
obj.set('fooAgain', 'barAgain');
const acl = new Parse.ACL();
var acl = new Parse.ACL();
// Make sure our update request can not query the object
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(true);
@@ -700,7 +700,7 @@ describe('miscellaneous', function() {
});
it('afterSave flattens custom operations', done => {
let triggerTime = 0;
var triggerTime = 0;
// Register a mock beforeSave hook
Parse.Cloud.afterSave('GameScore', function(req, res) {
const object = req.object;
@@ -721,7 +721,7 @@ describe('miscellaneous', function() {
res.success();
});
const obj = new Parse.Object('GameScore');
var obj = new Parse.Object('GameScore');
obj.increment('yolo', 1);
obj.save().then(() => {
obj.increment('yolo', 1);
@@ -817,7 +817,7 @@ describe('miscellaneous', function() {
it('should return the updated fields on PUT', done => {
const obj = new Parse.Object('GameScore');
obj.save({a:'hello', c: 1, d: ['1'], e:['1'], f:['1','2']}).then(() => {
const headers = {
var headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
@@ -825,7 +825,7 @@ describe('miscellaneous', function() {
};
request.put({
headers: headers,
url: 'http://localhost:8378/1/classes/GameScore/' + obj.id,
url: 'http://localhost:8378/1/classes/GameScore/'+obj.id,
body: JSON.stringify({
a: 'b',
c: {"__op":"Increment","amount":2},
@@ -921,7 +921,7 @@ describe('miscellaneous', function() {
expect(req.installationId).toEqual('yolo');
});
const headers = {
var headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
@@ -952,7 +952,7 @@ describe('miscellaneous', function() {
expect(req.installationId).toEqual('yolo');
});
const headers = {
var headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
@@ -979,7 +979,7 @@ describe('miscellaneous', function() {
Parse.Cloud.define('echoParams', (req, res) => {
res.success(req.params);
});
const headers = {
var headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'test'
@@ -994,7 +994,7 @@ describe('miscellaneous', function() {
body: '{"foo":"bar", "other": 1}'
}, (error, response, body) => {
expect(error).toBe(null);
const res = JSON.parse(body).result;
var res = JSON.parse(body).result;
expect(res.option).toEqual('1');
// Make sure query string params override body params
expect(res.other).toEqual('2');
@@ -1028,12 +1028,12 @@ describe('miscellaneous', function() {
});
Parse.Cloud.run('func', {nullParam: null})
.then(() => {
done()
}, () => {
fail('cloud code call failed');
done();
});
.then(() => {
done()
}, () => {
fail('cloud code call failed');
done();
});
});
it('can handle date params in cloud functions (#2214)', done => {
@@ -1045,16 +1045,16 @@ describe('miscellaneous', function() {
});
Parse.Cloud.run('dateFunc', {date: date})
.then(() => {
done()
}, () => {
fail('cloud code call failed');
done();
});
.then(() => {
done()
}, () => {
fail('cloud code call failed');
done();
});
});
it('fails on invalid client key', done => {
const headers = {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-Client-Key': 'notclient'
@@ -1064,14 +1064,14 @@ describe('miscellaneous', function() {
url: 'http://localhost:8378/1/classes/TestObject'
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.error).toEqual('unauthorized');
done();
});
});
it('fails on invalid windows key', done => {
const headers = {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-Windows-Key': 'notwindows'
@@ -1081,14 +1081,14 @@ describe('miscellaneous', function() {
url: 'http://localhost:8378/1/classes/TestObject'
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.error).toEqual('unauthorized');
done();
});
});
it('fails on invalid javascript key', done => {
const headers = {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-Javascript-Key': 'notjavascript'
@@ -1098,14 +1098,14 @@ describe('miscellaneous', function() {
url: 'http://localhost:8378/1/classes/TestObject'
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.error).toEqual('unauthorized');
done();
});
});
it('fails on invalid rest api key', done => {
const headers = {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'notrest'
@@ -1115,7 +1115,7 @@ describe('miscellaneous', function() {
url: 'http://localhost:8378/1/classes/TestObject'
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.error).toEqual('unauthorized');
done();
});
@@ -1241,7 +1241,7 @@ describe('miscellaneous', function() {
amount: amount
}
},
url: 'http://localhost:8378/1/classes/AnObject/' + object.id
url: 'http://localhost:8378/1/classes/AnObject/'+object.id
})
return new Promise((resolve, reject) => {
request.put(options, (err, res, body) => {
@@ -1296,94 +1296,94 @@ describe('miscellaneous', function() {
const obj = new Parse.Object('Obj');
obj.set('val', { createdAt: 'a', updatedAt: 1 });
obj.save()
.then(obj => new Parse.Query('Obj').get(obj.id))
.then(obj => {
expect(obj.get('val').createdAt).toEqual('a');
expect(obj.get('val').updatedAt).toEqual(1);
done();
});
.then(obj => new Parse.Query('Obj').get(obj.id))
.then(obj => {
expect(obj.get('val').createdAt).toEqual('a');
expect(obj.get('val').updatedAt).toEqual(1);
done();
});
});
it('bans interior keys containing . or $', done => {
new Parse.Object('Obj').save({innerObj: {'key with a $': 'fails'}})
.then(() => {
fail('should not succeed')
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
return new Parse.Object('Obj').save({innerObj: {'key with a .': 'fails'}});
})
.then(() => {
fail('should not succeed')
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
return new Parse.Object('Obj').save({innerObj: {innerInnerObj: {'key with $': 'fails'}}});
})
.then(() => {
fail('should not succeed')
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
return new Parse.Object('Obj').save({innerObj: {innerInnerObj: {'key with .': 'fails'}}});
})
.then(() => {
fail('should not succeed')
done();
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
done();
});
.then(() => {
fail('should not succeed')
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
return new Parse.Object('Obj').save({innerObj: {'key with a .': 'fails'}});
})
.then(() => {
fail('should not succeed')
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
return new Parse.Object('Obj').save({innerObj: {innerInnerObj: {'key with $': 'fails'}}});
})
.then(() => {
fail('should not succeed')
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
return new Parse.Object('Obj').save({innerObj: {innerInnerObj: {'key with .': 'fails'}}});
})
.then(() => {
fail('should not succeed')
done();
}, error => {
expect(error.code).toEqual(Parse.Error.INVALID_NESTED_KEY);
done();
});
});
it('does not change inner object keys named _auth_data_something', done => {
new Parse.Object('O').save({ innerObj: {_auth_data_facebook: 7}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_auth_data_facebook: 7});
done();
});
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_auth_data_facebook: 7});
done();
});
});
it('does not change inner object key names _p_somethign', done => {
new Parse.Object('O').save({ innerObj: {_p_data: 7}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_p_data: 7});
done();
});
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_p_data: 7});
done();
});
});
it('does not change inner object key names _rperm, _wperm', done => {
new Parse.Object('O').save({ innerObj: {_rperm: 7, _wperm: 8}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_rperm: 7, _wperm: 8});
done();
});
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_rperm: 7, _wperm: 8});
done();
});
});
it('does not change inner objects if the key has the same name as a geopoint field on the class, and the value is an array of length 2, or if the key has the same name as a file field on the class, and the value is a string', done => {
const file = new Parse.File('myfile.txt', { base64: 'eAo=' });
file.save()
.then(f => {
const obj = new Parse.Object('O');
obj.set('fileField', f);
obj.set('geoField', new Parse.GeoPoint(0, 0));
obj.set('innerObj', {
fileField: "data",
geoField: [1,2],
});
return obj.save();
})
.then(object => object.fetch())
.then(object => {
expect(object.get('innerObj')).toEqual({
fileField: "data",
geoField: [1,2],
});
done();
}).catch((e) => {
jfail(e);
done();
.then(f => {
const obj = new Parse.Object('O');
obj.set('fileField', f);
obj.set('geoField', new Parse.GeoPoint(0, 0));
obj.set('innerObj', {
fileField: "data",
geoField: [1,2],
});
return obj.save();
})
.then(object => object.fetch())
.then(object => {
expect(object.get('innerObj')).toEqual({
fileField: "data",
geoField: [1,2],
});
done();
}).catch((e) => {
jfail(e);
done();
});
});
it('purge all objects in class', (done) => {
@@ -1392,29 +1392,29 @@ describe('miscellaneous', function() {
const object2 = new Parse.Object('TestObject');
object2.set('alice', 'wonderland');
Parse.Object.saveAll([object, object2])
.then(() => {
.then(() => {
const query = new Parse.Query(TestObject);
return query.count()
}).then((count) => {
expect(count).toBe(2);
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
};
request.del({
headers: headers,
url: 'http://localhost:8378/1/purge/TestObject',
json: true
}, (err) => {
expect(err).toBe(null);
const query = new Parse.Query(TestObject);
return query.count()
}).then((count) => {
expect(count).toBe(2);
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
};
request.del({
headers: headers,
url: 'http://localhost:8378/1/purge/TestObject',
json: true
}, (err) => {
expect(err).toBe(null);
const query = new Parse.Query(TestObject);
return query.count().then((count) => {
expect(count).toBe(0);
done();
});
return query.count().then((count) => {
expect(count).toBe(0);
done();
});
});
});
});
it('fail on purge all objects in class without master key', (done) => {
@@ -1442,7 +1442,7 @@ describe('miscellaneous', function() {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
};
let user, object;
var user, object;
createTestUser().then((x) => {
user = x;
const acl = new Parse.ACL();
@@ -1493,11 +1493,6 @@ describe('miscellaneous', function() {
});
});
it('purge empty class', (done) => {
const testSchema = new Parse.Schema('UnknownClass');
testSchema.purge().then(done).catch(done.fail);
});
it('should not update schema beforeSave #2672', (done) => {
Parse.Cloud.beforeSave('MyObject', (request, response) => {
if (request.object.get('secret')) {
@@ -1551,10 +1546,10 @@ describe_only_db('mongo')('legacy _acl', () => {
},
json: true
}).then(() => {
const config = Config.get('test');
const config = new Config('test');
const adapter = config.database.adapter;
return adapter._adaptiveCollection("Report")
.then(collection => collection.find({}))
.then(collection => collection.find({}))
}).then((results) => {
expect(results.length).toBe(1);
const result = results[0];
+15 -15
View File
@@ -1,10 +1,10 @@
const ParseCloudCodePublisher = require('../src/LiveQuery/ParseCloudCodePublisher').ParseCloudCodePublisher;
const Parse = require('parse/node');
var ParseCloudCodePublisher = require('../src/LiveQuery/ParseCloudCodePublisher').ParseCloudCodePublisher;
var Parse = require('parse/node');
describe('ParseCloudCodePublisher', function() {
beforeEach(function(done) {
// Mock ParsePubSub
const mockParsePubSub = {
var mockParsePubSub = {
createPublisher: jasmine.createSpy('publish').and.returnValue({
publish: jasmine.createSpy('publish'),
on: jasmine.createSpy('on')
@@ -19,46 +19,46 @@ describe('ParseCloudCodePublisher', function() {
});
it('can initialize', function() {
const config = {}
var config = {}
new ParseCloudCodePublisher(config);
const ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
});
it('can handle cloud code afterSave request', function() {
const publisher = new ParseCloudCodePublisher({});
var publisher = new ParseCloudCodePublisher({});
publisher._onCloudCodeMessage = jasmine.createSpy('onCloudCodeMessage');
const request = {};
var request = {};
publisher.onCloudCodeAfterSave(request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterSave', request);
});
it('can handle cloud code afterDelete request', function() {
const publisher = new ParseCloudCodePublisher({});
var publisher = new ParseCloudCodePublisher({});
publisher._onCloudCodeMessage = jasmine.createSpy('onCloudCodeMessage');
const request = {};
var request = {};
publisher.onCloudCodeAfterDelete(request);
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterDelete', request);
});
it('can handle cloud code request', function() {
const publisher = new ParseCloudCodePublisher({});
const currentParseObject = new Parse.Object('Test');
var publisher = new ParseCloudCodePublisher({});
var currentParseObject = new Parse.Object('Test');
currentParseObject.set('key', 'value');
const originalParseObject = new Parse.Object('Test');
var originalParseObject = new Parse.Object('Test');
originalParseObject.set('key', 'originalValue');
const request = {
var request = {
object: currentParseObject,
original: originalParseObject
};
publisher._onCloudCodeMessage('afterSave', request);
const args = publisher.parsePublisher.publish.calls.mostRecent().args;
var args = publisher.parsePublisher.publish.calls.mostRecent().args;
expect(args[0]).toBe('afterSave');
const message = JSON.parse(args[1]);
var message = JSON.parse(args[1]);
expect(message.currentParseObject).toEqual(request.object._toFullJSON());
expect(message.originalParseObject).toEqual(request.original._toFullJSON());
});
+72 -358
View File
@@ -3,18 +3,18 @@
"use strict";
const request = require('request');
var request = require('request');
const str = "Hello World!";
const data = [];
for (let i = 0; i < str.length; i++) {
var str = "Hello World!";
var data = [];
for (var i = 0; i < str.length; i++) {
data.push(str.charCodeAt(i));
}
describe('Parse.File testing', () => {
describe('creating files', () => {
it('works with Content-Type', done => {
const headers = {
var headers = {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -25,7 +25,7 @@ describe('Parse.File testing', () => {
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.name).toMatch(/_file.txt$/);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*file.txt$/);
request.get(b.url, (error, response, body) => {
@@ -49,7 +49,7 @@ describe('Parse.File testing', () => {
})
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.name).toMatch(/_file.html/);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*file.html$/);
request.get(b.url, (error, response, body) => {
@@ -66,7 +66,7 @@ describe('Parse.File testing', () => {
});
it('works without Content-Type', done => {
const headers = {
var headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
@@ -76,7 +76,7 @@ describe('Parse.File testing', () => {
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.name).toMatch(/_file.txt$/);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*file.txt$/);
request.get(b.url, (error, response, body) => {
@@ -89,7 +89,7 @@ describe('Parse.File testing', () => {
});
it('supports REST end-to-end file create, read, delete, read', done => {
const headers = {
var headers = {
'Content-Type': 'image/jpeg',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -100,7 +100,7 @@ describe('Parse.File testing', () => {
body: 'check one two',
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.name).toMatch(/_testfile.txt$/);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*testfile.txt$/);
request.get(b.url, (error, response, body) => {
@@ -137,7 +137,7 @@ describe('Parse.File testing', () => {
});
it('blocks file deletions with missing or incorrect master-key header', done => {
const headers = {
var headers = {
'Content-Type': 'image/jpeg',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -148,7 +148,7 @@ describe('Parse.File testing', () => {
body: 'the file body'
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*thefile.jpg$/);
// missing X-Parse-Master-Key header
request.del({
@@ -159,7 +159,7 @@ describe('Parse.File testing', () => {
url: 'http://localhost:8378/1/files/' + b.name
}, (error, response, body) => {
expect(error).toBe(null);
const del_b = JSON.parse(body);
var del_b = JSON.parse(body);
expect(response.statusCode).toEqual(403);
expect(del_b.error).toMatch(/unauthorized/);
// incorrect X-Parse-Master-Key header
@@ -172,7 +172,7 @@ describe('Parse.File testing', () => {
url: 'http://localhost:8378/1/files/' + b.name
}, (error, response, body) => {
expect(error).toBe(null);
const del_b2 = JSON.parse(body);
var del_b2 = JSON.parse(body);
expect(response.statusCode).toEqual(403);
expect(del_b2.error).toMatch(/unauthorized/);
done();
@@ -182,7 +182,7 @@ describe('Parse.File testing', () => {
});
it('handles other filetypes', done => {
const headers = {
var headers = {
'Content-Type': 'image/jpeg',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -193,7 +193,7 @@ describe('Parse.File testing', () => {
body: 'argle bargle',
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.name).toMatch(/_file.jpg$/);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/.*file.jpg$/);
request.get(b.url, (error, response, body) => {
@@ -205,7 +205,7 @@ describe('Parse.File testing', () => {
});
it("save file", done => {
const file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
ok(!file.url());
file.save(expectSuccess({
success: function(result) {
@@ -219,7 +219,7 @@ describe('Parse.File testing', () => {
});
it("save file in object", done => {
const file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
ok(!file.url());
file.save(expectSuccess({
success: function(result) {
@@ -228,7 +228,7 @@ describe('Parse.File testing', () => {
ok(file.url());
notEqual(file.name(), "hello.txt");
const object = new Parse.Object("TestObject");
var object = new Parse.Object("TestObject");
object.save({
file: file
}, expectSuccess({
@@ -246,7 +246,7 @@ describe('Parse.File testing', () => {
});
it("save file in object with escaped characters in filename", done => {
const file = new Parse.File("hello . txt", data, "text/plain");
var file = new Parse.File("hello . txt", data, "text/plain");
ok(!file.url());
file.save(expectSuccess({
success: function(result) {
@@ -255,7 +255,7 @@ describe('Parse.File testing', () => {
ok(file.url());
notEqual(file.name(), "hello . txt");
const object = new Parse.Object("TestObject");
var object = new Parse.Object("TestObject");
object.save({
file: file
}, expectSuccess({
@@ -274,9 +274,9 @@ describe('Parse.File testing', () => {
});
it("autosave file in object", done => {
let file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
ok(!file.url());
const object = new Parse.Object("TestObject");
var object = new Parse.Object("TestObject");
object.save({
file: file
}, expectSuccess({
@@ -296,22 +296,22 @@ describe('Parse.File testing', () => {
});
it("autosave file in object in object", done => {
let file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
ok(!file.url());
const child = new Parse.Object("Child");
var child = new Parse.Object("Child");
child.set("file", file);
const parent = new Parse.Object("Parent");
var parent = new Parse.Object("Parent");
parent.set("child", child);
parent.save(expectSuccess({
success: function(parent) {
const query = new Parse.Query("Parent");
var query = new Parse.Query("Parent");
query.include("child");
query.get(parent.id, expectSuccess({
success: function(parentAgain) {
const childAgain = parentAgain.get("child");
var childAgain = parentAgain.get("child");
file = childAgain.get("file");
ok(file instanceof Parse.File);
ok(file.name());
@@ -325,7 +325,7 @@ describe('Parse.File testing', () => {
});
it("saving an already saved file", done => {
const file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
ok(!file.url());
file.save(expectSuccess({
success: function(result) {
@@ -333,7 +333,7 @@ describe('Parse.File testing', () => {
ok(file.name());
ok(file.url());
notEqual(file.name(), "hello.txt");
const previousName = file.name();
var previousName = file.name();
file.save(expectSuccess({
success: function() {
@@ -346,13 +346,13 @@ describe('Parse.File testing', () => {
});
it("two saves at the same time", done => {
const file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
let firstName;
let secondName;
var firstName;
var secondName;
const firstSave = file.save().then(function() { firstName = file.name(); });
const secondSave = file.save().then(function() { secondName = file.name(); });
var firstSave = file.save().then(function() { firstName = file.name(); });
var secondSave = file.save().then(function() { secondName = file.name(); });
Parse.Promise.when(firstSave, secondSave).then(function() {
equal(firstName, secondName);
@@ -364,9 +364,9 @@ describe('Parse.File testing', () => {
});
it("file toJSON testing", done => {
const file = new Parse.File("hello.txt", data, "text/plain");
var file = new Parse.File("hello.txt", data, "text/plain");
ok(!file.url());
const object = new Parse.Object("TestObject");
var object = new Parse.Object("TestObject");
object.save({
file: file
}, expectSuccess({
@@ -378,7 +378,7 @@ describe('Parse.File testing', () => {
});
it("content-type used with no extension", done => {
const headers = {
var headers = {
'Content-Type': 'text/html',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -389,7 +389,7 @@ describe('Parse.File testing', () => {
body: 'fee fi fo',
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.name).toMatch(/\.html$/);
request.get(b.url, (error, response) => {
if (!response) {
@@ -403,7 +403,7 @@ describe('Parse.File testing', () => {
});
it("filename is url encoded", done => {
const headers = {
var headers = {
'Content-Type': 'text/html',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -414,26 +414,26 @@ describe('Parse.File testing', () => {
body: 'oh emm gee',
}, (error, response, body) => {
expect(error).toBe(null);
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.url).toMatch(/hello%20world/);
done();
})
});
it('supports array of files', done => {
const file = {
var file = {
__type: 'File',
url: 'http://meep.meep',
name: 'meep'
};
const files = [file, file];
const obj = new Parse.Object('FilesArrayTest');
var files = [file, file];
var obj = new Parse.Object('FilesArrayTest');
obj.set('files', files);
obj.save().then(() => {
const query = new Parse.Query('FilesArrayTest');
var query = new Parse.Query('FilesArrayTest');
return query.first();
}).then((result) => {
const filesAgain = result.get('files');
var filesAgain = result.get('files');
expect(filesAgain.length).toEqual(2);
expect(filesAgain[0].name()).toEqual('meep');
expect(filesAgain[0].url()).toEqual('http://meep.meep');
@@ -442,7 +442,7 @@ describe('Parse.File testing', () => {
});
it('validates filename characters', done => {
const headers = {
var headers = {
'Content-Type': 'text/plain',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
@@ -452,19 +452,19 @@ describe('Parse.File testing', () => {
url: 'http://localhost:8378/1/files/di$avowed.txt',
body: 'will fail',
}, (error, response, body) => {
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.code).toEqual(122);
done();
});
});
it('validates filename length', done => {
const headers = {
var headers = {
'Content-Type': 'text/plain',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
const fileName = 'Onceuponamidnightdrearywhileiponderedweak' +
var fileName = 'Onceuponamidnightdrearywhileiponderedweak' +
'andwearyOveramanyquaintandcuriousvolumeof' +
'forgottenloreWhileinoddednearlynappingsud' +
'denlytherecameatapping';
@@ -473,30 +473,30 @@ describe('Parse.File testing', () => {
url: 'http://localhost:8378/1/files/' + fileName,
body: 'will fail',
}, (error, response, body) => {
const b = JSON.parse(body);
var b = JSON.parse(body);
expect(b.code).toEqual(122);
done();
});
});
it('supports a dictionary with file', done => {
const file = {
var file = {
__type: 'File',
url: 'http://meep.meep',
name: 'meep'
};
const dict = {
var dict = {
file: file
};
const obj = new Parse.Object('FileObjTest');
var obj = new Parse.Object('FileObjTest');
obj.set('obj', dict);
obj.save().then(() => {
const query = new Parse.Query('FileObjTest');
var query = new Parse.Query('FileObjTest');
return query.first();
}).then((result) => {
const dictAgain = result.get('obj');
var dictAgain = result.get('obj');
expect(typeof dictAgain).toEqual('object');
const fileAgain = dictAgain['file'];
var fileAgain = dictAgain['file'];
expect(fileAgain.name()).toEqual('meep');
expect(fileAgain.url()).toEqual('http://meep.meep');
done();
@@ -507,18 +507,18 @@ describe('Parse.File testing', () => {
});
it('creates correct url for old files hosted on files.parsetfss.com', done => {
const file = {
var file = {
__type: 'File',
url: 'http://irrelevant.elephant/',
name: 'tfss-123.txt'
};
const obj = new Parse.Object('OldFileTest');
var obj = new Parse.Object('OldFileTest');
obj.set('oldfile', file);
obj.save().then(() => {
const query = new Parse.Query('OldFileTest');
var query = new Parse.Query('OldFileTest');
return query.first();
}).then((result) => {
const fileAgain = result.get('oldfile');
var fileAgain = result.get('oldfile');
expect(fileAgain.url()).toEqual(
'http://files.parsetfss.com/test/tfss-123.txt'
);
@@ -530,18 +530,18 @@ describe('Parse.File testing', () => {
});
it('creates correct url for old files hosted on files.parse.com', done => {
const file = {
var file = {
__type: 'File',
url: 'http://irrelevant.elephant/',
name: 'd6e80979-a128-4c57-a167-302f874700dc-123.txt'
};
const obj = new Parse.Object('OldFileTest');
var obj = new Parse.Object('OldFileTest');
obj.set('oldfile', file);
obj.save().then(() => {
const query = new Parse.Query('OldFileTest');
var query = new Parse.Query('OldFileTest');
return query.first();
}).then((result) => {
const fileAgain = result.get('oldfile');
var fileAgain = result.get('oldfile');
expect(fileAgain.url()).toEqual(
'http://files.parse.com/test/d6e80979-a128-4c57-a167-302f874700dc-123.txt'
);
@@ -553,14 +553,14 @@ describe('Parse.File testing', () => {
});
it('supports files in objects without urls', done => {
const file = {
var file = {
__type: 'File',
name: '123.txt'
};
const obj = new Parse.Object('FileTest');
var obj = new Parse.Object('FileTest');
obj.set('file', file);
obj.save().then(() => {
const query = new Parse.Query('FileTest');
var query = new Parse.Query('FileTest');
return query.first();
}).then(result => {
const fileAgain = result.get('file');
@@ -576,15 +576,15 @@ describe('Parse.File testing', () => {
reconfigureServer({
publicServerURL: 'https://mydomain/parse'
}).then(() => {
const file = {
var file = {
__type: 'File',
name: '123.txt'
};
const obj = new Parse.Object('FileTest');
var obj = new Parse.Object('FileTest');
obj.set('file', file);
return obj.save()
}).then(() => {
const query = new Parse.Query('FileTest');
var query = new Parse.Query('FileTest');
return query.first();
}).then(result => {
const fileAgain = result.get('file');
@@ -595,290 +595,4 @@ describe('Parse.File testing', () => {
done();
});
});
it('fails to upload an empty file', done => {
const 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 => {
const 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 => {
const 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 => {
const 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 => {
const 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);
const 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 => {
const 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);
const 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 => {
const 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);
const 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 => {
const 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);
const 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 => {
const 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);
const 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) {
let s = string;
while (count > 0) {
s += string;
count--;
}
return s;
}
it('supports large range requests', done => {
const 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);
const 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 => {
const 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);
const 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();
});
});
});
});
});
+68 -491
View File
@@ -1,23 +1,21 @@
// This is a port of the test suite:
// hungry/js/test/parse_geo_point_test.js
const rp = require('request-promise');
const TestObject = Parse.Object.extend('TestObject');
var TestObject = Parse.Object.extend('TestObject');
describe('Parse.GeoPoint testing', () => {
it('geo point roundtrip', (done) => {
const point = new Parse.GeoPoint(44.0, -11.0);
const obj = new TestObject();
var point = new Parse.GeoPoint(44.0, -11.0);
var obj = new TestObject();
obj.set('location', point);
obj.set('name', 'Ferndale');
obj.save(null, {
success: function() {
const query = new Parse.Query(TestObject);
var query = new Parse.Query(TestObject);
query.find({
success: function(results) {
equal(results.length, 1);
const pointAgain = results[0].get('location');
var pointAgain = results[0].get('location');
ok(pointAgain);
equal(pointAgain.latitude, 44.0);
equal(pointAgain.longitude, -11.0);
@@ -28,49 +26,9 @@ 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(() => {
const 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 => {
const point = new Parse.GeoPoint(44.0, -11.0);
const 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('creating geo point exception two fields', (done) => {
const point = new Parse.GeoPoint(20, 20);
const obj = new TestObject();
it('geo point exception two fields', (done) => {
var point = new Parse.GeoPoint(20, 20);
var obj = new TestObject();
obj.set('locationOne', point);
obj.set('locationTwo', point);
obj.save().then(() => {
@@ -81,29 +39,11 @@ describe('Parse.GeoPoint testing', () => {
});
});
// TODO: This should also have support in postgres, or higher level database agnostic support.
it_exclude_dbs(['postgres'])('updating geo point exception two fields', (done) => {
const point = new Parse.GeoPoint(20, 20);
const obj = new TestObject();
obj.set('locationOne', point);
obj.save(null, {
success: (obj) => {
obj.set('locationTwo', point);
obj.save().then(() => {
fail('expected error');
}, (err) => {
equal(err.code, Parse.Error.INCORRECT_TYPE);
done();
})
}
});
});
it('geo line', (done) => {
const line = [];
for (let i = 0; i < 10; ++i) {
const obj = new TestObject();
const point = new Parse.GeoPoint(i * 4.0 - 12.0, i * 3.2 - 11.0);
var line = [];
for (var i = 0; i < 10; ++i) {
var obj = new TestObject();
var point = new Parse.GeoPoint(i * 4.0 - 12.0, i * 3.2 - 11.0);
obj.set('location', point);
obj.set('construct', 'line');
obj.set('seq', i);
@@ -111,8 +51,8 @@ describe('Parse.GeoPoint testing', () => {
}
Parse.Object.saveAll(line, {
success: function() {
const query = new Parse.Query(TestObject);
const point = new Parse.GeoPoint(24, 19);
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(24, 19);
query.equalTo('construct', 'line');
query.withinMiles('location', point, 10000);
query.find({
@@ -128,17 +68,17 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance large', (done) => {
const objects = [];
var objects = [];
[0, 1, 2].map(function(i) {
const obj = new TestObject();
const point = new Parse.GeoPoint(0.0, i * 45.0);
var obj = new TestObject();
var point = new Parse.GeoPoint(0.0, i * 45.0);
obj.set('location', point);
obj.set('index', i);
objects.push(obj);
});
Parse.Object.saveAll(objects).then(() => {
const query = new Parse.Query(TestObject);
const point = new Parse.GeoPoint(1.0, -1.0);
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(1.0, -1.0);
query.withinRadians('location', point, 3.14);
return query.find();
}).then((results) => {
@@ -151,17 +91,17 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance medium', (done) => {
const objects = [];
var objects = [];
[0, 1, 2].map(function(i) {
const obj = new TestObject();
const point = new Parse.GeoPoint(0.0, i * 45.0);
var obj = new TestObject();
var point = new Parse.GeoPoint(0.0, i * 45.0);
obj.set('location', point);
obj.set('index', i);
objects.push(obj);
});
Parse.Object.saveAll(objects, function() {
const query = new Parse.Query(TestObject);
const point = new Parse.GeoPoint(1.0, -1.0);
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(1.0, -1.0);
query.withinRadians('location', point, 3.14 * 0.5);
query.find({
success: function(results) {
@@ -175,17 +115,17 @@ describe('Parse.GeoPoint testing', () => {
});
it('geo max distance small', (done) => {
const objects = [];
var objects = [];
[0, 1, 2].map(function(i) {
const obj = new TestObject();
const point = new Parse.GeoPoint(0.0, i * 45.0);
var obj = new TestObject();
var point = new Parse.GeoPoint(0.0, i * 45.0);
obj.set('location', point);
obj.set('index', i);
objects.push(obj);
});
Parse.Object.saveAll(objects, function() {
const query = new Parse.Query(TestObject);
const point = new Parse.GeoPoint(1.0, -1.0);
var query = new Parse.Query(TestObject);
var point = new Parse.GeoPoint(1.0, -1.0);
query.withinRadians('location', point, 3.14 * 0.25);
query.find({
success: function(results) {
@@ -197,16 +137,16 @@ describe('Parse.GeoPoint testing', () => {
});
});
const makeSomeGeoPoints = function(callback) {
const sacramento = new TestObject();
var makeSomeGeoPoints = function(callback) {
var sacramento = new TestObject();
sacramento.set('location', new Parse.GeoPoint(38.52, -121.50));
sacramento.set('name', 'Sacramento');
const honolulu = new TestObject();
var honolulu = new TestObject();
honolulu.set('location', new Parse.GeoPoint(21.35, -157.93));
honolulu.set('name', 'Honolulu');
const sf = new TestObject();
var sf = new TestObject();
sf.set('location', new Parse.GeoPoint(37.75, -122.68));
sf.set('name', 'San Francisco');
@@ -215,8 +155,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in km everywhere', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
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 ;)
query.withinKilometers('location', sfo, 4800.0);
query.find({
@@ -230,8 +170,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in km california', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinKilometers('location', sfo, 3700.0);
query.find({
success: function(results) {
@@ -246,8 +186,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in km bay area', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinKilometers('location', sfo, 100.0);
query.find({
success: function(results) {
@@ -261,8 +201,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in km mid peninsula', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinKilometers('location', sfo, 10.0);
query.find({
success: function(results) {
@@ -275,8 +215,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in miles everywhere', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinMiles('location', sfo, 2600.0);
query.find({
success: function(results) {
@@ -289,8 +229,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in miles california', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinMiles('location', sfo, 2200.0);
query.find({
success: function(results) {
@@ -305,8 +245,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in miles bay area', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
// 100km is 62 miles...
query.withinMiles('location', sfo, 62.0);
query.find({
@@ -321,8 +261,8 @@ describe('Parse.GeoPoint testing', () => {
it('geo max distance in miles mid peninsula', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
var query = new Parse.Query(TestObject);
query.withinMiles('location', sfo, 10.0);
query.find({
success: function(results) {
@@ -333,51 +273,34 @@ describe('Parse.GeoPoint testing', () => {
});
});
it('returns nearest location', (done) => {
makeSomeGeoPoints(function() {
const sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
const query = new Parse.Query(TestObject);
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);
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(() => {
var query = new Parse.Query(TestObject);
query.withinGeoBox('point', southwestOfSF, northeastOfSF);
return query.find();
}).then((results) => {
equal(results.length, 2);
equal(results.length, 1);
done();
});
});
it('supports a sub-object with a geo point', done => {
const point = new Parse.GeoPoint(44.0, -11.0);
const obj = new TestObject();
var point = new Parse.GeoPoint(44.0, -11.0);
var obj = new TestObject();
obj.set('subobject', { location: point });
obj.save(null, {
success: function() {
const query = new Parse.Query(TestObject);
var query = new Parse.Query(TestObject);
query.find({
success: function(results) {
equal(results.length, 1);
const pointAgain = results[0].get('subobject')['location'];
var pointAgain = results[0].get('subobject')['location'];
ok(pointAgain);
equal(pointAgain.latitude, 44.0);
equal(pointAgain.longitude, -11.0);
@@ -389,17 +312,17 @@ describe('Parse.GeoPoint testing', () => {
});
it('supports array of geo points', done => {
const point1 = new Parse.GeoPoint(44.0, -11.0);
const point2 = new Parse.GeoPoint(22.0, -55.0);
const obj = new TestObject();
var point1 = new Parse.GeoPoint(44.0, -11.0);
var point2 = new Parse.GeoPoint(22.0, -55.0);
var obj = new TestObject();
obj.set('locations', [ point1, point2 ]);
obj.save(null, {
success: function() {
const query = new Parse.Query(TestObject);
var query = new Parse.Query(TestObject);
query.find({
success: function(results) {
equal(results.length, 1);
const locations = results[0].get('locations');
var locations = results[0].get('locations');
expect(locations.length).toEqual(2);
expect(locations[0]).toEqual(point1);
expect(locations[1]).toEqual(point2);
@@ -409,350 +332,4 @@ describe('Parse.GeoPoint testing', () => {
}
});
});
it('equalTo geopoint', (done) => {
const point = new Parse.GeoPoint(44.0, -11.0);
const 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('supports withinPolygon Polygon object', (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});
const polygon = {
__type: 'Polygon',
coordinates: [
[0, 0],
[10, 0],
[10, 10],
[0, 10],
[0, 0]
]
}
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoWithin: {
$polygon: 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) => {
expect(resp.results.length).toBe(2);
done();
}, done.fail);
});
it('invalid Polygon object withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
const polygon = {
__type: 'Polygon',
coordinates: [
[0, 0],
[10, 0],
]
}
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: 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('out of bounds Polygon object withinPolygon', (done) => {
const point = new Parse.GeoPoint(1.5, 1.5);
const obj = new Parse.Object('Polygon', {location: point});
const polygon = {
__type: 'Polygon',
coordinates: [
[0, 0],
[181, 0],
[0, 10]
]
}
obj.save().then(() => {
const where = {
location: {
$geoWithin: {
$polygon: 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(1);
done();
});
});
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();
});
});
});
+3 -46
View File
@@ -1,11 +1,11 @@
'use strict';
const request = require('request');
var request = require('request');
const Config = require('../src/Config');
describe('a GlobalConfig', () => {
beforeEach(done => {
const config = Config.get('test');
const config = new Config('test');
const query = on_db('mongo', () => {
// Legacy is with an int...
return { objectId: 1 };
@@ -56,49 +56,6 @@ 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',
@@ -147,7 +104,7 @@ describe('a GlobalConfig', () => {
});
it('failed getting config when it is missing', (done) => {
const config = Config.get('test');
const config = new Config('test');
config.database.adapter.deleteObjectsByQuery(
'_GlobalConfig',
{ fields: { params: { __type: 'String' } } },
+132 -133
View File
@@ -1,16 +1,16 @@
"use strict";
/* global describe, it, expect, fail, Parse */
const request = require('request');
const triggers = require('../src/triggers');
const HooksController = require('../src/Controllers/HooksController').default;
const express = require("express");
const bodyParser = require('body-parser');
var request = require('request');
var triggers = require('../src/triggers');
var HooksController = require('../src/Controllers/HooksController').default;
var express = require("express");
var bodyParser = require('body-parser');
const port = 12345;
const hookServerURL = "http://localhost:" + port;
var port = 12345;
var hookServerURL = "http://localhost:"+port;
const AppCache = require('../src/cache').AppCache;
const app = express();
var app = express();
app.use(bodyParser.json({ 'type': '*/*' }))
app.listen(12345);
@@ -38,51 +38,51 @@ describe('Hooks', () => {
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(() => {
.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)
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);
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();
})
});
it("should CRUD a trigger registration", (done) => {
// Create
// 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
// Find
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
}, (err) => {
fail(err);
@@ -92,7 +92,7 @@ describe('Hooks', () => {
expect(res).not.toBe(undefined);
expect(res.objectId).toBeUndefined();
expect(res.url).toBe("http://someurl");
// delete
// delete
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
}, (err) => {
jfail(err);
@@ -107,7 +107,7 @@ describe('Hooks', () => {
jfail(err);
done();
}).then(() => {
// Find again! but should be deleted
// Find again! but should be deleted
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
}, (err) => {
jfail(err);
@@ -129,7 +129,7 @@ describe('Hooks', () => {
});
it("should fail to register hooks without Master Key", (done) => {
request.post(Parse.serverURL + "/hooks/functions", {
request.post(Parse.serverURL+"/hooks/functions", {
headers: {
"X-Parse-Application-Id": Parse.applicationId,
"X-Parse-REST-API-Key": Parse.restKey,
@@ -143,28 +143,26 @@ describe('Hooks', () => {
});
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(() => {
fail("should not be able to create the same function");
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('function name: my_new_function already exits')
}
return Parse.Hooks.removeFunction("my_new_function");
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
})
Parse.Hooks.createFunction("my_new_function", "http://url.com").then(() => {
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
}, () => {
fail("should create a new function");
}).then(() => {
fail("should not be able to create the same function");
}, (err) => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(143);
expect(err.message).toBe('function name: my_new_function already exits')
}
return Parse.Hooks.removeFunction("my_new_function");
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
})
});
it("should fail trying to create two times the same trigger", (done) => {
@@ -256,7 +254,7 @@ describe('Hooks', () => {
});
it("should fail trying to create a malformed function (REST)", (done) => {
request.post(Parse.serverURL + "/hooks/functions", {
request.post(Parse.serverURL+"/hooks/functions", {
headers: {
"X-Parse-Application-Id": Parse.applicationId,
"X-Parse-Master-Key": Parse.masterKey,
@@ -273,19 +271,19 @@ describe('Hooks', () => {
it("should create hooks and properly preload them", (done) => {
const promises = [];
for (let 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(){
for (let 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();
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()
@@ -294,9 +292,9 @@ describe('Hooks', () => {
fail('Should properly create all hooks');
done();
}).then(function() {
for (let i = 0; i < 5; i++) {
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).not.toBeUndefined();
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) => {
@@ -312,7 +310,7 @@ describe('Hooks', () => {
res.json({success:"OK!"});
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunction").then(function(){
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
@@ -333,8 +331,8 @@ describe('Hooks', () => {
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(){
// 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);
@@ -364,7 +362,7 @@ describe('Hooks', () => {
}
});
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKey").then(function(){
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKey").then(function(){
return Parse.Cloud.run("SOME_TEST_FUNCTION")
}, (err) => {
jfail(err);
@@ -382,49 +380,49 @@ describe('Hooks', () => {
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"});
}
});
.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(){
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();
});
});
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) => {
let triggerCount = 0;
var triggerCount = 0;
app.post("/BeforeSaveSome", function(req, res) {
triggerCount++;
const object = req.body.object;
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
// 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 () {
// 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) {
@@ -442,11 +440,11 @@ describe('Hooks', () => {
it("beforeSave hooks should correctly handle responses containing entire object", (done) => {
app.post("/BeforeSaveSome2", function(req, res) {
const object = Parse.Object.fromJSON(req.body.object);
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(){
Parse.Hooks.createTrigger("SomeRandomObject2", "beforeSave" ,hookServerURL+"/BeforeSaveSome2").then(function(){
const obj = new Parse.Object("SomeRandomObject2");
return obj.save();
}).then(function(res) {
@@ -461,30 +459,31 @@ describe('Hooks', () => {
});
it("should run the afterSave hook on the test server", (done) => {
let triggerCount = 0;
let newObjectId;
var triggerCount = 0;
var newObjectId;
app.post("/AfterSaveSome", function(req, res) {
triggerCount++;
const obj = new Parse.Object("AnotherObject");
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(){
// 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() {
const promise = new Parse.Promise();
// Wait a bit here as it's an after save
setTimeout(() => {
var promise = new Parse.Promise();
// Wait a bit here as it's an after save
setTimeout(function(){
expect(triggerCount).toBe(1);
new Parse.Query("AnotherObject")
.get(newObjectId)
.then((r) => promise.resolve(r));
}, 500);
var q = new Parse.Query("AnotherObject");
q.get(newObjectId).then(function(r){
promise.resolve(r);
});
}, 300)
return promise;
}).then(function(res){
expect(res.get("foo")).toEqual("bar");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+249 -249
View File
File diff suppressed because it is too large Load Diff
-340
View File
@@ -1,340 +0,0 @@
const TestObject = Parse.Object.extend('TestObject');
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const rp = require('request-promise');
const 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', new Parse.Polygon(coords));
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closed);
done();
}, done.fail);
});
it('polygon save closed path', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, coords);
done();
}, done.fail);
});
it('polygon equalTo (open/closed) path', (done) => {
const openPoints = [[0,0],[0,1],[1,1],[1,0]];
const closedPoints = [[0,0],[0,1],[1,1],[1,0],[0,0]];
const openPolygon = new Parse.Polygon(openPoints);
const closedPolygon = new Parse.Polygon(closedPoints);
const obj = new TestObject();
obj.set('polygon', openPolygon);
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
query.equalTo('polygon', openPolygon);
return query.find();
}).then((results) => {
const polygon = results[0].get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closedPoints);
const query = new Parse.Query(TestObject);
query.equalTo('polygon', closedPolygon);
return query.find();
}).then((results) => {
const polygon = results[0].get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closedPoints);
done();
}, done.fail);
});
it('polygon update', (done) => {
const oldCoords = [[0,0],[0,1],[1,1],[1,0]];
const oldPolygon = new Parse.Polygon(oldCoords);
const newCoords = [[2,2],[2,3],[3,3],[3,2]];
const newPolygon = new Parse.Polygon(newCoords);
const obj = new TestObject();
obj.set('polygon', oldPolygon);
return obj.save().then(() => {
obj.set('polygon', newPolygon);
return obj.save();
}).then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
newCoords.push(newCoords[0]);
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, newCoords);
done();
}, done.fail);
});
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();
// use raw so we test the server validates properly
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', new Parse.Polygon(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', new Parse.Polygon(coords));
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon instanceof Parse.Polygon, true);
equal(polygon.coordinates, closed);
done();
}, done.fail);
});
it('polygonContain query', (done) => {
const points1 = [[0,0],[0,1],[1,1],[1,0]];
const points2 = [[0,0],[0,2],[2,2],[2,0]];
const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]];
const polygon1 = new Parse.Polygon(points1);
const polygon2 = new Parse.Polygon(points2);
const polygon3 = new Parse.Polygon(points3);
const obj1 = new TestObject({location: polygon1});
const obj2 = new TestObject({location: polygon2});
const obj3 = new TestObject({location: polygon3});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 }
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then((resp) => {
expect(resp.results.length).toBe(2);
done();
}, done.fail);
});
it('polygonContain query no reverse input (Regression test for #4608)', (done) => {
const points1 = [[.25,0],[.25,1.25],[.75,1.25],[.75,0]];
const points2 = [[0,0],[0,2],[2,2],[2,0]];
const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]];
const polygon1 = new Parse.Polygon(points1);
const polygon2 = new Parse.Polygon(points2);
const polygon3 = new Parse.Polygon(points3);
const obj1 = new TestObject({location: polygon1});
const obj2 = new TestObject({location: polygon2});
const obj3 = new TestObject({location: polygon3});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 0.5, longitude:1.0 }
}
}
};
return 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 query real data (Regression test for #4608)', (done) => {
const detroit = [[42.631655189280224,-83.78406753121705],[42.633047793854814,-83.75333640366955],[42.61625254348911,-83.75149921669944],[42.61526926650296,-83.78161794858735],[42.631655189280224,-83.78406753121705]];
const polygon = new Parse.Polygon(detroit);
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 42.624599, longitude:-83.770162 }
}
}
};
return 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(1);
done();
}, done.fail);
});
it('polygonContain invalid input', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = new Parse.Polygon(points);
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 }
}
}
};
return 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 = new Parse.Polygon(points);
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: []
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then(done.fail, () => done());
});
});
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]];
// testings against REST API, use raw formats
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 coordinates reverse input', (done) => {
const Config = require('../src/Config');
const config = Config.get('test');
// When stored the first point should be the last point
const input = [[12,11],[14,13],[16,15],[18,17]];
const output = [[[11,12],[13,14],[15,16],[17,18],[11,12]]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(input));
obj.save().then(() => {
return config.database.adapter._rawFind('TestObject', {_id: obj.id});
}).then((results) => {
expect(results.length).toBe(1);
expect(results[0].polygon.coordinates).toEqual(output);
done();
});
});
it('polygon loop is not valid', (done) => {
const coords = [[0,0],[0,1],[1,0],[1,1]];
const obj = new TestObject();
obj.set('polygon', new Parse.Polygon(coords));
obj.save().then(done.fail, done);
});
});
+15 -15
View File
@@ -1,16 +1,16 @@
const ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
describe('ParsePubSub', function() {
beforeEach(function(done) {
// Mock RedisPubSub
const mockRedisPubSub = {
var mockRedisPubSub = {
createPublisher: jasmine.createSpy('createPublisherRedis'),
createSubscriber: jasmine.createSpy('createSubscriberRedis')
};
jasmine.mockLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub', mockRedisPubSub);
// Mock EventEmitterPubSub
const mockEventEmitterPubSub = {
var mockEventEmitterPubSub = {
createPublisher: jasmine.createSpy('createPublisherEventEmitter'),
createSubscriber: jasmine.createSpy('createSubscriberEventEmitter')
};
@@ -23,8 +23,8 @@ describe('ParsePubSub', function() {
redisURL: 'redisURL'
});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith({redisURL: 'redisURL'});
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
});
@@ -32,8 +32,8 @@ describe('ParsePubSub', function() {
it('can create event emitter publisher', function() {
ParsePubSub.createPublisher({});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createPublisher).toHaveBeenCalled();
});
@@ -43,8 +43,8 @@ describe('ParsePubSub', function() {
redisURL: 'redisURL'
});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith({redisURL: 'redisURL'});
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
});
@@ -52,8 +52,8 @@ describe('ParsePubSub', function() {
it('can create event emitter subscriber', function() {
ParsePubSub.createSubscriber({});
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createSubscriber).toHaveBeenCalled();
});
@@ -73,8 +73,8 @@ describe('ParsePubSub', function() {
});
expect(adapter.createSubscriber).toHaveBeenCalled();
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
@@ -100,8 +100,8 @@ describe('ParsePubSub', function() {
});
expect(adapter.createSubscriber).toHaveBeenCalled();
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
-747
View File
@@ -1,747 +0,0 @@
'use strict';
const Parse = require('parse/node');
const rp = require('request-promise');
const masterKeyHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'test',
'X-Parse-Master-Key': 'test'
}
const masterKeyOptions = {
headers: masterKeyHeaders,
json: true
}
const PointerObject = Parse.Object.extend({
className: "PointerObject"
});
const loadTestData = () => {
const data1 = {score: 10, name: 'foo', sender: {group: 'A'}, views: 900, size: ['S', 'M']};
const data2 = {score: 10, name: 'foo', sender: {group: 'A'}, views: 800, size: ['M', 'L']};
const data3 = {score: 10, name: 'bar', sender: {group: 'B'}, views: 700, size: ['S']};
const data4 = {score: 20, name: 'dpl', sender: {group: 'B'}, views: 700, size: ['S']};
const obj1 = new TestObject(data1);
const obj2 = new TestObject(data2);
const obj3 = new TestObject(data3);
const obj4 = new TestObject(data4);
return Parse.Object.saveAll([obj1, obj2, obj3, obj4]);
}
describe('Parse.Query Aggregate testing', () => {
beforeEach((done) => {
loadTestData().then(done, done);
});
it('should only query aggregate with master key', (done) => {
Parse._request('GET', `aggregate/someClass`, {})
.then(() => {}, (error) => {
expect(error.message).toEqual('unauthorized: master key is required');
done();
});
});
it('invalid query invalid key', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
unknown: {},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.catch((error) => {
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('invalid query group _id', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { _id: null },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.catch((error) => {
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('invalid query group objectId required', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: {},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.catch((error) => {
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('group by field', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: '$name' },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(3);
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[1].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[2].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).not.toBe(undefined);
expect(resp.results[1].objectId).not.toBe(undefined);
expect(resp.results[2].objectId).not.toBe(undefined);
done();
}).catch(done.fail);
});
it('group by empty object', (done) => {
const obj = new TestObject();
const pipeline = [{
group: { objectId: {} }
}];
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results[0].objectId).toEqual(null);
done();
});
});
it('group by empty string', (done) => {
const obj = new TestObject();
const pipeline = [{
group: { objectId: '' }
}];
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results[0].objectId).toEqual(null);
done();
});
});
it('group by empty array', (done) => {
const obj = new TestObject();
const pipeline = [{
group: { objectId: [] }
}];
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results[0].objectId).toEqual(null);
done();
});
});
it('group by date object', (done) => {
const obj1 = new TestObject();
const obj2 = new TestObject();
const obj3 = new TestObject();
const pipeline = [{
group: {
objectId: { day: { $dayOfMonth: "$_updated_at" }, month: { $month: "$_created_at" }, year: { $year: "$_created_at" } },
count: { $sum: 1 }
}
}];
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
const createdAt = new Date(obj1.createdAt);
expect(results[0].objectId.day).toEqual(createdAt.getUTCDate());
expect(results[0].objectId.month).toEqual(createdAt.getMonth() + 1);
expect(results[0].objectId.year).toEqual(createdAt.getUTCFullYear());
done();
});
});
it_exclude_dbs(['postgres'])('cannot group by date field (excluding createdAt and updatedAt)', (done) => {
const obj1 = new TestObject({ dateField: new Date(1990, 11, 1) });
const obj2 = new TestObject({ dateField: new Date(1990, 5, 1) });
const obj3 = new TestObject({ dateField: new Date(1990, 11, 1) });
const pipeline = [{
group: {
objectId: { day: { $dayOfMonth: "$dateField" }, month: { $month: "$dateField" }, year: { $year: "$dateField" } },
count: { $sum: 1 }
}
}];
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then(done.fail).catch((error) => {
expect(error.code).toEqual(Parse.Error.INVALID_QUERY);
done();
});
});
it('group by pointer', (done) => {
const pointer1 = new TestObject();
const pointer2 = new TestObject();
const obj1 = new TestObject({ pointer: pointer1 });
const obj2 = new TestObject({ pointer: pointer2 });
const obj3 = new TestObject({ pointer: pointer1 });
const pipeline = [
{ group: { objectId: '$pointer' } }
];
Parse.Object.saveAll([pointer1, pointer2, obj1, obj2, obj3]).then(() => {
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results.length).toEqual(3);
expect(results.some(result => result.objectId === pointer1.id)).toEqual(true);
expect(results.some(result => result.objectId === pointer2.id)).toEqual(true);
expect(results.some(result => result.objectId === null)).toEqual(true);
done();
});
});
it('group sum query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].total).toBe(50);
done();
}).catch(done.fail);
});
it('group count query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: 1 } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].total).toBe(4);
done();
}).catch(done.fail);
});
it('group min query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, minScore: { $min: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].minScore).toBe(10);
done();
}).catch(done.fail);
});
it('group max query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, maxScore: { $max: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].maxScore).toBe(20);
done();
}).catch(done.fail);
});
it('group avg query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, avgScore: { $avg: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].avgScore).toBe(12.5);
done();
}).catch(done.fail);
});
it('limit query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
limit: 2,
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
done();
}).catch(done.fail);
});
it('sort ascending query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
sort: { name: 1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(4);
expect(resp.results[0].name).toBe('bar');
expect(resp.results[1].name).toBe('dpl');
expect(resp.results[2].name).toBe('foo');
expect(resp.results[3].name).toBe('foo');
done();
}).catch(done.fail);
});
it('sort decending query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
sort: { name: -1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(4);
expect(resp.results[0].name).toBe('foo');
expect(resp.results[1].name).toBe('foo');
expect(resp.results[2].name).toBe('dpl');
expect(resp.results[3].name).toBe('bar');
done();
}).catch(done.fail);
});
it('skip query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
skip: 2,
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
done();
}).catch(done.fail);
});
it('match comparison query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: { score: { $gt: 15 }},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(1);
expect(resp.results[0].score).toBe(20);
done();
}).catch(done.fail);
});
it('match multiple comparison query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: { score: { $gt: 5, $lt: 15 }},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(3);
expect(resp.results[0].score).toBe(10);
expect(resp.results[1].score).toBe(10);
expect(resp.results[2].score).toBe(10);
done();
}).catch(done.fail);
});
it('match complex comparison query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: { score: { $gt: 5, $lt: 15 }, views: { $gt: 850, $lt: 1000 }},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(1);
expect(resp.results[0].score).toBe(10);
expect(resp.results[0].views).toBe(900);
done();
}).catch(done.fail);
});
it('match comparison and equality query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: { score: { $gt: 5, $lt: 15 }, views: 900},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(1);
expect(resp.results[0].score).toBe(10);
expect(resp.results[0].views).toBe(900);
done();
}).catch(done.fail);
});
it('match $or query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: { $or: [{ score: { $gt: 15, $lt: 25 } }, { views: { $gt: 750, $lt: 850 } }]},
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
// Match score { $gt: 15, $lt: 25 }
expect(resp.results.some(result => result.score === 20)).toEqual(true);
expect(resp.results.some(result => result.views === 700)).toEqual(true);
// Match view { $gt: 750, $lt: 850 }
expect(resp.results.some(result => result.score === 10)).toEqual(true);
expect(resp.results.some(result => result.views === 800)).toEqual(true);
done();
}).catch(done.fail);
});
it('match objectId query', (done) => {
const obj1 = new TestObject();
const obj2 = new TestObject();
Parse.Object.saveAll([obj1, obj2]).then(() => {
const pipeline = [
{ match: { objectId: obj1.id } }
];
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results.length).toEqual(1);
expect(results[0].objectId).toEqual(obj1.id);
done();
});
});
it('match field query', (done) => {
const obj1 = new TestObject({ name: 'TestObject1'});
const obj2 = new TestObject({ name: 'TestObject2'});
Parse.Object.saveAll([obj1, obj2]).then(() => {
const pipeline = [
{ match: { name: 'TestObject1' } }
];
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results.length).toEqual(1);
expect(results[0].objectId).toEqual(obj1.id);
done();
});
});
it('match pointer query', (done) => {
const pointer1 = new PointerObject();
const pointer2 = new PointerObject();
const obj1 = new TestObject({ pointer: pointer1 });
const obj2 = new TestObject({ pointer: pointer2 });
const obj3 = new TestObject({ pointer: pointer1 });
Parse.Object.saveAll([pointer1, pointer2, obj1, obj2, obj3]).then(() => {
const pipeline = [
{ match: { pointer: pointer1.id } }
];
const query = new Parse.Query(TestObject);
return query.aggregate(pipeline);
}).then((results) => {
expect(results.length).toEqual(2);
expect(results[0].pointer.objectId).toEqual(pointer1.id);
expect(results[1].pointer.objectId).toEqual(pointer1.id);
expect(results.some(result => result.objectId === obj1.id)).toEqual(true);
expect(results.some(result => result.objectId === obj3.id)).toEqual(true);
done();
});
});
it('project query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
project: { name: 1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
resp.results.forEach((result) => {
expect(result.objectId).not.toBe(undefined);
expect(result.name).not.toBe(undefined);
expect(result.sender).toBe(undefined);
expect(result.size).toBe(undefined);
expect(result.score).toBe(undefined);
});
done();
}).catch(done.fail);
});
it('multiple project query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
project: { name: 1, score: 1, sender: 1 },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
resp.results.forEach((result) => {
expect(result.objectId).not.toBe(undefined);
expect(result.name).not.toBe(undefined);
expect(result.score).not.toBe(undefined);
expect(result.sender).not.toBe(undefined);
expect(result.size).toBe(undefined);
});
done();
}).catch(done.fail);
});
it('project with group query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
project: { score: 1 },
group: { objectId: '$score', score: { $sum: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
resp.results.forEach((result) => {
expect(result.hasOwnProperty('objectId')).toBe(true);
expect(result.name).toBe(undefined);
expect(result.sender).toBe(undefined);
expect(result.size).toBe(undefined);
expect(result.score).not.toBe(undefined);
if (result.objectId === 10) {
expect(result.score).toBe(30);
}
if (result.objectId === 20) {
expect(result.score).toBe(20);
}
});
done();
}).catch(done.fail);
});
it('class does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: '$score' } },
}
});
rp.get(Parse.serverURL + '/aggregate/UnknownClass', options)
.then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('field does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { objectId: null, total: { $sum: '$unknownfield' } },
}
});
rp.get(Parse.serverURL + '/aggregate/UnknownClass', options)
.then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('distinct query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'score' }
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
expect(resp.results.includes(10)).toBe(true);
expect(resp.results.includes(20)).toBe(true);
done();
}).catch(done.fail);
});
it('distinct query with where', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
distinct: 'score',
where: {
name: 'bar'
}
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0]).toBe(10);
done();
}).catch(done.fail);
});
it('distinct query with where string', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
distinct: 'score',
where: JSON.stringify({name:'bar'}),
}
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results[0]).toBe(10);
done();
}).catch(done.fail);
});
it('distinct nested', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'sender.group' }
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(2);
expect(resp.results.includes('A')).toBe(true);
expect(resp.results.includes('B')).toBe(true);
done();
}).catch(done.fail);
});
it('distinct pointer', (done) => {
const pointer1 = new PointerObject();
const pointer2 = new PointerObject();
const obj1 = new TestObject({ pointer: pointer1 });
const obj2 = new TestObject({ pointer: pointer2 });
const obj3 = new TestObject({ pointer: pointer1 });
Parse.Object.saveAll([pointer1, pointer2, obj1, obj2, obj3]).then(() => {
const query = new Parse.Query(TestObject);
return query.distinct('pointer');
}).then((results) => {
expect(results.length).toEqual(2);
expect(results.some(result => result.objectId === pointer1.id)).toEqual(true);
expect(results.some(result => result.objectId === pointer2.id)).toEqual(true);
done();
});
});
it('distinct class does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'unknown' }
});
rp.get(Parse.serverURL + '/aggregate/UnknownClass', options)
.then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('distinct field does not exist return empty', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'unknown' }
});
const obj = new TestObject();
obj.save().then(() => {
return rp.get(Parse.serverURL + '/aggregate/TestObject', options);
}).then((resp) => {
expect(resp.results.length).toBe(0);
done();
}).catch(done.fail);
});
it('distinct array', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'size' }
});
rp.get(Parse.serverURL + '/aggregate/TestObject', options)
.then((resp) => {
expect(resp.results.length).toBe(3);
expect(resp.results.includes('S')).toBe(true);
expect(resp.results.includes('M')).toBe(true);
expect(resp.results.includes('L')).toBe(true);
done();
}).catch(done.fail);
});
it('distinct null field', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: { distinct: 'distinctField' }
});
const user1 = new Parse.User();
user1.setUsername('distinct_1');
user1.setPassword('password');
user1.set('distinctField', 'one');
const user2 = new Parse.User();
user2.setUsername('distinct_2');
user2.setPassword('password');
user2.set('distinctField', null);
user1.signUp().then(() => {
return user2.signUp();
}).then(() => {
return rp.get(Parse.serverURL + '/aggregate/_User', options);
}).then((resp) => {
expect(resp.results.length).toEqual(1);
expect(resp.results).toEqual(['one']);
done();
}).catch(done.fail);
});
it('does not return sensitive hidden properties', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: {
score: {
$gt: 5
}
},
}
});
const username = 'leaky_user';
const score = 10;
const user = new Parse.User();
user.setUsername(username);
user.setPassword('password');
user.set('score', score);
user.signUp().then(function() {
return rp.get(Parse.serverURL + '/aggregate/_User', options);
}).then(function(resp) {
expect(resp.results.length).toBe(1);
const result = resp.results[0];
// verify server-side keys are not present...
expect(result._hashed_password).toBe(undefined);
expect(result._wperm).toBe(undefined);
expect(result._rperm).toBe(undefined);
expect(result._acl).toBe(undefined);
expect(result._created_at).toBe(undefined);
expect(result._updated_at).toBe(undefined);
// verify createdAt, updatedAt and others are present
expect(result.createdAt).not.toBe(undefined);
expect(result.updatedAt).not.toBe(undefined);
expect(result.objectId).not.toBe(undefined);
expect(result.username).toBe(username);
expect(result.score).toBe(score);
done();
}).catch(function(err) {
fail(err);
});
});
});
-512
View File
@@ -1,512 +0,0 @@
'use strict';
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
import PostgresStorageAdapter from '../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],
comment: 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: does not create text index if compound index exist', (done) => {
fullTextHelper().then(() => {
return databaseAdapter.dropAllIndexes('TestObject');
}).then(() => {
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
expect(indexes.length).toEqual(1);
return databaseAdapter.createIndex('TestObject', {subject: 'text', comment: 'text'});
}).then(() => {
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
expect(indexes.length).toEqual(2);
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).toEqual(3);
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
expect(indexes.length).toEqual(2);
rp.get({
url: 'http://localhost:8378/1/schemas/TestObject',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
json: true,
}, (error, response, body) => {
expect(body.indexes._id_).toBeDefined();
expect(body.indexes._id_._id).toEqual(1);
expect(body.indexes.subject_text_comment_text).toBeDefined();
expect(body.indexes.subject_text_comment_text.subject).toEqual('text');
expect(body.indexes.subject_text_comment_text.comment).toEqual('text');
done();
});
}).catch(done.fail);
});
it('fullTextSearch: does not create text index if schema compound index exist', (done) => {
fullTextHelper().then(() => {
return databaseAdapter.dropAllIndexes('TestObject');
}).then(() => {
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
expect(indexes.length).toEqual(1);
return rp.put({
url: 'http://localhost:8378/1/schemas/TestObject',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'test',
'X-Parse-Master-Key': 'test',
},
body: {
indexes: {
text_test: { subject: 'text', comment: 'text'},
},
},
});
}).then(() => {
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
expect(indexes.length).toEqual(2);
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).toEqual(3);
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
expect(indexes.length).toEqual(2);
rp.get({
url: 'http://localhost:8378/1/schemas/TestObject',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
json: true,
}, (error, response, body) => {
expect(body.indexes._id_).toBeDefined();
expect(body.indexes._id_._id).toEqual(1);
expect(body.indexes.text_test).toBeDefined();
expect(body.indexes.text_test.subject).toEqual('text');
expect(body.indexes.text_test.comment).toEqual('text');
done();
});
}).catch(done.fail);
});
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();
});
});
});
+506 -1658
View File
File diff suppressed because it is too large Load Diff
+177 -246
View File
@@ -2,16 +2,16 @@
// This is a port of the test suite:
// hungry/js/test/parse_relation_test.js
const ChildObject = Parse.Object.extend({className: "ChildObject"});
const ParentObject = Parse.Object.extend({className: "ParentObject"});
var ChildObject = Parse.Object.extend({className: "ChildObject"});
var ParentObject = Parse.Object.extend({className: "ParentObject"});
describe('Parse.Relation testing', () => {
it("simple add and remove relation", (done) => {
const child = new ChildObject();
var child = new ChildObject();
child.set("x", 2);
const parent = new ParentObject();
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("child");
var relation = parent.relation("child");
child.save().then(() => {
relation.add(child);
@@ -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,38 +34,38 @@ 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();
});
});
it("query relation without schema", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x:i}));
}
Parse.Object.saveAll(childObjects, expectSuccess({
success: function() {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("child");
var relation = parent.relation("child");
relation.add(childObjects[0]);
parent.save(null, expectSuccess({
success: function() {
const parentAgain = new ParentObject();
var parentAgain = new ParentObject();
parentAgain.id = parent.id;
const relation = parentAgain.relation("child");
var relation = parentAgain.relation("child");
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();
}
}));
@@ -77,33 +77,33 @@ describe('Parse.Relation testing', () => {
it("relations are constructed right from query", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects, {
success: function() {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("child");
var relation = parent.relation("child");
relation.add(childObjects[0]);
parent.save(null, {
success: function() {
const query = new Parse.Query(ParentObject);
var query = new Parse.Query(ParentObject);
query.get(parent.id, {
success: function(object) {
const relationAgain = object.relation("child");
var relationAgain = object.relation("child");
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() {
@@ -122,17 +122,17 @@ describe('Parse.Relation testing', () => {
});
it("compound add and remove relation", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
let parent;
let relation;
var parent;
var relation;
Parse.Object.saveAll(childObjects).then(function() {
const ParentObject = Parse.Object.extend('ParentObject');
var ParentObject = Parse.Object.extend('ParentObject');
parent = new ParentObject();
parent.set('x', 4);
relation = parent.relation('child');
@@ -163,63 +163,36 @@ describe('Parse.Relation testing', () => {
});
});
it("related at ordering optimizations", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
let parent;
let relation;
Parse.Object.saveAll(childObjects).then(function() {
const ParentObject = Parse.Object.extend('ParentObject');
parent = new ParentObject();
parent.set('x', 4);
relation = parent.relation('child');
relation.add(childObjects);
return parent.save();
}).then(function() {
const query = relation.query();
query.descending('createdAt');
query.skip(1);
query.limit(3);
return query.find();
}).then(function(list) {
expect(list.length).toBe(3);
}).then(done, done.fail);
});
it_exclude_dbs(['postgres'])("queries with relations", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects, {
success: function() {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("child");
var relation = parent.relation("child");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
parent.save(null, {
success: function() {
const query = relation.query();
var query = relation.query();
query.equalTo("x", 2);
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();
}
});
@@ -230,34 +203,34 @@ describe('Parse.Relation testing', () => {
});
it("queries on relation fields", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects, {
success: function() {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("child");
var relation = parent.relation("child");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
const parent2 = new ParentObject();
var parent2 = new ParentObject();
parent2.set("x", 3);
const relation2 = parent2.relation("child");
var relation2 = parent2.relation("child");
relation2.add(childObjects[4]);
relation2.add(childObjects[5]);
relation2.add(childObjects[6]);
const parents = [];
var parents = [];
parents.push(parent);
parents.push(parent2);
Parse.Object.saveAll(parents, {
success: function() {
const query = new Parse.Query(ParentObject);
const objects = [];
var query = new Parse.Query(ParentObject);
var objects = [];
objects.push(childObjects[4]);
objects.push(childObjects[9]);
query.containedIn("child", objects);
@@ -265,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();
}
});
@@ -324,32 +297,32 @@ describe('Parse.Relation testing', () => {
});
it_exclude_dbs(['postgres'])("query on pointer and relation fields with equal", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects).then(() => {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("toChilds");
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
const parent2 = new ParentObject();
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.set("toChild", childObjects[2]);
const parents = [];
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
return Parse.Object.saveAll(parents).then(() => {
const query = new Parse.Query(ParentObject);
var query = new Parse.Query(ParentObject);
query.equalTo("objectId", parent.id);
query.equalTo("toChilds", childObjects[2]);
@@ -365,35 +338,35 @@ describe('Parse.Relation testing', () => {
});
it("query on pointer and relation fields with equal bis", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects).then(() => {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("toChilds");
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
const parent2 = new ParentObject();
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.relation("toChilds").add(childObjects[2]);
const parents = [];
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
return Parse.Object.saveAll(parents).then(() => {
const query = new Parse.Query(ParentObject);
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
// childObjects[2] is in 2 relations
// before the fix, that woul yield 2 results
query.equalTo("toChilds", childObjects[2]);
return query.find().then((list) => {
@@ -405,38 +378,38 @@ describe('Parse.Relation testing', () => {
});
it_exclude_dbs(['postgres'])("or queries on pointer and relation fields", (done) => {
const ChildObject = Parse.Object.extend("ChildObject");
const childObjects = [];
for (let i = 0; i < 10; i++) {
var ChildObject = Parse.Object.extend("ChildObject");
var childObjects = [];
for (var i = 0; i < 10; i++) {
childObjects.push(new ChildObject({x: i}));
}
Parse.Object.saveAll(childObjects).then(() => {
const ParentObject = Parse.Object.extend("ParentObject");
const parent = new ParentObject();
var ParentObject = Parse.Object.extend("ParentObject");
var parent = new ParentObject();
parent.set("x", 4);
const relation = parent.relation("toChilds");
var relation = parent.relation("toChilds");
relation.add(childObjects[0]);
relation.add(childObjects[1]);
relation.add(childObjects[2]);
const parent2 = new ParentObject();
var parent2 = new ParentObject();
parent2.set("x", 3);
parent2.set("toChild", childObjects[2]);
const parents = [];
var parents = [];
parents.push(parent);
parents.push(parent2);
parents.push(new ParentObject());
return Parse.Object.saveAll(parents).then(() => {
const query1 = new Parse.Query(ParentObject);
var query1 = new Parse.Query(ParentObject);
query1.containedIn("toChilds", [childObjects[2]]);
const query2 = new Parse.Query(ParentObject);
var query2 = new Parse.Query(ParentObject);
query2.equalTo("toChild", childObjects[2]);
const query = Parse.Query.or(query1, query2);
var query = Parse.Query.or(query1, query2);
return query.find().then((list) => {
const objectIds = list.map(function(item){
var objectIds = list.map(function(item){
return item.id;
});
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
@@ -451,21 +424,21 @@ describe('Parse.Relation testing', () => {
it("Get query on relation using un-fetched parent object", (done) => {
// Setup data model
const Wheel = Parse.Object.extend('Wheel');
const Car = Parse.Object.extend('Car');
const origWheel = new Wheel();
var Wheel = Parse.Object.extend('Wheel');
var Car = Parse.Object.extend('Car');
var origWheel = new Wheel();
origWheel.save().then(function() {
const car = new Car();
const relation = car.relation('wheels');
var car = new Car();
var relation = car.relation('wheels');
relation.add(origWheel);
return car.save();
}).then(function(car) {
// Test starts here.
// Create an un-fetched shell car object
const unfetchedCar = new Car();
var unfetchedCar = new Car();
unfetchedCar.id = car.id;
const relation = unfetchedCar.relation('wheels');
const query = relation.query();
var relation = unfetchedCar.relation('wheels');
var query = relation.query();
// Parent object is un-fetched, so this will call /1/classes/Car instead
// of /1/classes/Wheel and pass { "redirectClassNameForKey":"wheels" }.
@@ -484,28 +457,28 @@ describe('Parse.Relation testing', () => {
it("Find query on relation using un-fetched parent object", (done) => {
// Setup data model
const Wheel = Parse.Object.extend('Wheel');
const Car = Parse.Object.extend('Car');
const origWheel = new Wheel();
var Wheel = Parse.Object.extend('Wheel');
var Car = Parse.Object.extend('Car');
var origWheel = new Wheel();
origWheel.save().then(function() {
const car = new Car();
const relation = car.relation('wheels');
var car = new Car();
var relation = car.relation('wheels');
relation.add(origWheel);
return car.save();
}).then(function(car) {
// Test starts here.
// Create an un-fetched shell car object
const unfetchedCar = new Car();
var unfetchedCar = new Car();
unfetchedCar.id = car.id;
const relation = unfetchedCar.relation('wheels');
const query = relation.query();
var relation = unfetchedCar.relation('wheels');
var query = relation.query();
// Parent object is un-fetched, so this will call /1/classes/Car instead
// of /1/classes/Wheel and pass { "redirectClassNameForKey":"wheels" }.
return query.find(origWheel.id);
}).then(function(results) {
// Make sure this is Wheel and not Car.
const wheel = results[0];
var wheel = results[0];
strictEqual(wheel.className, 'Wheel');
strictEqual(wheel.id, origWheel.id);
}).then(function() {
@@ -518,16 +491,16 @@ describe('Parse.Relation testing', () => {
it('Find objects with a related object using equalTo', (done) => {
// Setup the objects
const Card = Parse.Object.extend('Card');
const House = Parse.Object.extend('House');
const card = new Card();
var Card = Parse.Object.extend('Card');
var House = Parse.Object.extend('House');
var card = new Card();
card.save().then(() => {
const house = new House();
const relation = house.relation('cards');
var house = new House();
var relation = house.relation('cards');
relation.add(card);
return house.save();
}).then(() => {
const query = new Parse.Query('House');
var query = new Parse.Query('House');
query.equalTo('cards', card);
return query.find();
}).then((results) => {
@@ -602,17 +575,14 @@ describe('Parse.Relation testing', () => {
expect(result.get('key').get('even')).toBe(false);
});
done();
}, (e) => {
fail(JSON.stringify(e));
done();
})
});
it("select query", function(done) {
const RestaurantObject = Parse.Object.extend("Restaurant");
const PersonObject = Parse.Object.extend("Person");
const OwnerObject = Parse.Object.extend('Owner');
const restaurants = [
var RestaurantObject = Parse.Object.extend("Restaurant");
var PersonObject = Parse.Object.extend("Person");
var OwnerObject = Parse.Object.extend('Owner');
var restaurants = [
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
];
@@ -630,9 +600,9 @@ describe('Parse.Relation testing', () => {
}).then(() => {
const unfetchedOwner = new OwnerObject();
unfetchedOwner.id = owner.id;
const query = unfetchedOwner.relation('restaurants').query();
var query = unfetchedOwner.relation('restaurants').query();
query.greaterThan("ratings", 4);
const mainQuery = new Parse.Query(PersonObject);
var mainQuery = new Parse.Query(PersonObject);
mainQuery.matchesKeyInQuery("hometown", "location", query);
mainQuery.find(expectSuccess({
success: function(results) {
@@ -643,17 +613,14 @@ describe('Parse.Relation testing', () => {
done();
}
}));
}, (e) => {
fail(JSON.stringify(e));
done();
});
});
it("dontSelect query", function(done) {
const RestaurantObject = Parse.Object.extend("Restaurant");
const PersonObject = Parse.Object.extend("Person");
const OwnerObject = Parse.Object.extend('Owner');
const restaurants = [
var RestaurantObject = Parse.Object.extend("Restaurant");
var PersonObject = Parse.Object.extend("Person");
var OwnerObject = Parse.Object.extend('Owner');
var restaurants = [
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
];
@@ -671,9 +638,9 @@ describe('Parse.Relation testing', () => {
}).then(() => {
const unfetchedOwner = new OwnerObject();
unfetchedOwner.id = owner.id;
const query = unfetchedOwner.relation('restaurants').query();
var query = unfetchedOwner.relation('restaurants').query();
query.greaterThan("ratings", 4);
const mainQuery = new Parse.Query(PersonObject);
var mainQuery = new Parse.Query(PersonObject);
mainQuery.doesNotMatchKeyInQuery("hometown", "location", query);
mainQuery.ascending('name');
mainQuery.find(expectSuccess({
@@ -686,9 +653,6 @@ describe('Parse.Relation testing', () => {
done();
}
}));
}, (e) => {
fail(JSON.stringify(e));
done();
});
});
@@ -724,114 +688,81 @@ describe('Parse.Relation testing', () => {
const query = new Parse.Query(Parse.Role);
query.equalTo('name', 'admin');
query.first({ useMasterKey: true })
.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('Should have found admin user, errored instead');
fail(error);
.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('Should have found admin user, errored instead');
fail(error);
done();
});
});
const adminUser = new Parse.User();
adminUser.set('username', 'name');
adminUser.set('password', 'pass');
adminUser.signUp()
.then(adminUser => {
const adminACL = new Parse.ACL();
adminACL.setPublicReadAccess(true);
.then(adminUser => {
const adminACL = new Parse.ACL();
adminACL.setPublicReadAccess(true);
// 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()
});
// 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 sign up');
fail('failed to save role');
fail(error);
done();
done()
});
}, error => {
fail('failed to sign up');
fail(error);
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);
.then(() => {
const newRelation = obj1.relation('aRelation');
newRelation.add(obj1);
obj1.save().then(() => {
const relation = obj1.get('aRelation');
obj1.set('aRelation', relation);
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();
});
done();
}, error => {
fail('failed to create relation field');
fail('failed to save ParseRelation object');
fail(error);
done();
});
}, error => {
fail('failed to save obj');
fail('failed to create relation field');
fail(error);
done();
});
});
it('ensures beforeFind on relation doesnt side effect', (done) => {
const parent = new Parse.Object('Parent');
const child = new Parse.Object('Child');
child.save().then(() => {
parent.relation('children').add(child);
return parent.save();
}).then(() => {
// We need to use a new reference otherwise the JS SDK remembers the className for a relation
// After saves or finds
const otherParent = new Parse.Object('Parent');
otherParent.id = parent.id;
return otherParent.relation('children').query().find();
}).then((children) => {
// Without an after find all is good, all results have been redirected with proper className
children.forEach((child) => expect(child.className).toBe('Child'));
// Setup the afterFind
Parse.Cloud.afterFind('Child', (req) => {
return Promise.resolve(req.objects.map((child) => {
child.set('afterFound', true);
return child;
}));
});
const otherParent = new Parse.Object('Parent');
otherParent.id = parent.id;
return otherParent.relation('children').query().find();
}).then((children) => {
children.forEach((child) => {
expect(child.className).toBe('Child');
expect(child.get('afterFound')).toBe(true);
});
}).then(done).catch(done.fail);
}, error => {
fail('failed to save obj');
fail(error);
done();
});
});
});
+67 -184
View File
@@ -2,14 +2,14 @@
// Roles are not accessible without the master key, so they are not intended
// for use by clients. We can manually test them using the master key.
const RestQuery = require("../src/RestQuery");
const Auth = require("../src/Auth").Auth;
const Config = require("../src/Config");
var RestQuery = require("../src/RestQuery");
var Auth = require("../src/Auth").Auth;
var Config = require("../src/Config");
describe('Parse Role testing', () => {
it('Do a bunch of basic role testing', done => {
let user;
let role;
var user;
var role;
createTestUser().then((x) => {
user = x;
@@ -19,23 +19,23 @@ describe('Parse Role testing', () => {
role = new Parse.Object('_Role');
role.set('name', 'Foos');
role.setACL(acl);
const users = role.relation('users');
var users = role.relation('users');
users.add(user);
return role.save({}, { useMasterKey: true });
}).then(() => {
const query = new Parse.Query('_Role');
var query = new Parse.Query('_Role');
return query.find({ useMasterKey: true });
}).then((x) => {
expect(x.length).toEqual(1);
const relation = x[0].relation('users').query();
var relation = x[0].relation('users').query();
return relation.first({ useMasterKey: true });
}).then((x) => {
expect(x.id).toEqual(user.id);
// Here we've got a valid role and a user assigned.
// Lets create an object only the role can read/write and test
// the different scenarios.
const obj = new Parse.Object('TestObject');
const acl = new Parse.ACL();
var obj = new Parse.Object('TestObject');
var acl = new Parse.ACL();
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setRoleReadAccess('Foos', true);
@@ -43,11 +43,11 @@ describe('Parse Role testing', () => {
obj.setACL(acl);
return obj.save();
}).then(() => {
const query = new Parse.Query('TestObject');
var query = new Parse.Query('TestObject');
return query.find({ sessionToken: user.getSessionToken() });
}).then((x) => {
expect(x.length).toEqual(1);
const objAgain = x[0];
var objAgain = x[0];
objAgain.set('foo', 'bar');
// This should succeed:
return objAgain.save({}, {sessionToken: user.getSessionToken()});
@@ -64,10 +64,10 @@ describe('Parse Role testing', () => {
});
const createRole = function(name, sibling, user) {
const role = new Parse.Role(name, new Parse.ACL());
var createRole = function(name, sibling, user) {
var role = new Parse.Role(name, new Parse.ACL());
if (user) {
const users = role.relation('users');
var users = role.relation('users');
users.add(user);
}
if (sibling) {
@@ -77,13 +77,13 @@ describe('Parse Role testing', () => {
};
it("should not recursively load the same role multiple times", (done) => {
const rootRole = "RootRole";
const roleNames = ["FooRole", "BarRole", "BazRole"];
const allRoles = [rootRole].concat(roleNames);
var rootRole = "RootRole";
var roleNames = ["FooRole", "BarRole", "BazRole"];
var allRoles = [rootRole].concat(roleNames);
const roleObjs = {};
const createAllRoles = function(user) {
const promises = allRoles.map(function(roleName) {
var roleObjs = {};
var createAllRoles = function(user) {
var promises = allRoles.map(function(roleName) {
return createRole(roleName, null, user)
.then(function(roleObj) {
roleObjs[roleName] = roleObj;
@@ -93,16 +93,16 @@ describe('Parse Role testing', () => {
return Promise.all(promises);
};
const restExecute = spyOn(RestQuery.prototype, "execute").and.callThrough();
var restExecute = spyOn(RestQuery.prototype, "execute").and.callThrough();
let user,
var user,
auth,
getAllRolesSpy;
createTestUser().then((newUser) => {
user = newUser;
return createAllRoles(user);
}).then ((roles) => {
const rootRoleObj = roleObjs[rootRole];
var rootRoleObj = roleObjs[rootRole];
roles.forEach(function(role, i) {
// Add all roles to the RootRole
if (role.id !== rootRoleObj.id) {
@@ -116,7 +116,7 @@ describe('Parse Role testing', () => {
return Parse.Object.saveAll(roles, { useMasterKey: true });
}).then(() => {
auth = new Auth({config: Config.get("test"), isMaster: true, user: user});
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
getAllRolesSpy = spyOn(auth, "_getAllRolesNamesForRoleIds").and.callThrough();
return auth._loadRoles();
@@ -124,7 +124,7 @@ describe('Parse Role testing', () => {
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
@@ -143,29 +143,29 @@ describe('Parse Role testing', () => {
});
it("should recursively load roles", (done) => {
const rolesNames = ["FooRole", "BarRole", "BazRole"];
const roleIds = {};
var rolesNames = ["FooRole", "BarRole", "BazRole"];
var roleIds = {};
createTestUser().then((user) => {
// Put the user on the 1st role
// 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
// 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
// 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;
const auth = new Auth({ config: Config.get("test"), isMaster: true, user: user });
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);
expect(roles.indexOf('role:'+name)).not.toBe(-1);
});
done();
}, function(){
@@ -175,38 +175,20 @@ describe('Parse Role testing', () => {
});
it("_Role object should not save without name.", (done) => {
const role = new Parse.Role();
var role = new Parse.Role();
role.save(null,{useMasterKey:true})
.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();
.then(() => {
fail("_Role object should not save without name.");
}, (error) => {
expect(error.code).toEqual(137);
done();
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();
});
});
});
@@ -222,7 +204,7 @@ describe('Parse Role testing', () => {
superContentManager.getRoles().add(superModerator);
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
}).then(() => {
const auth = new Auth({ config: Config.get("test"), isMaster: true });
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
const promises = [admin, moderator, contentManager, superModerator].map((role) => {
@@ -264,12 +246,12 @@ describe('Parse Role testing', () => {
});
it('can create role and query empty users', (done)=> {
const roleACL = new Parse.ACL();
var roleACL = new Parse.ACL();
roleACL.setPublicReadAccess(true);
const role = new Parse.Role('subscribers', roleACL);
var role = new Parse.Role('subscribers', roleACL);
role.save({}, {useMasterKey : true})
.then(()=>{
const query = role.relation('users').query();
var query = role.relation('users').query();
query.find({useMasterKey : true})
.then(()=>{
done();
@@ -284,13 +266,13 @@ describe('Parse Role testing', () => {
// Based on various scenarios described in issues #827 and #683,
it('should properly handle role permissions on objects', (done) => {
let user, user2, user3;
let role, role2, role3;
let obj, obj2;
var user, user2, user3;
var role, role2, role3;
var obj, obj2;
const prACL = new Parse.ACL();
var prACL = new Parse.ACL();
prACL.setPublicReadAccess(true);
let adminACL, superACL, customerACL;
var adminACL, superACL, customerACL;
createTestUser().then((x) => {
user = x;
@@ -328,7 +310,7 @@ describe('Parse Role testing', () => {
customerACL.setRoleReadAccess("Customer", true);
customerACL.setRoleWriteAccess("Customer", true);
const query = new Parse.Query('_Role');
var query = new Parse.Query('_Role');
return query.find({ useMasterKey: true });
}).then((x) => {
expect(x.length).toEqual(3);
@@ -366,11 +348,11 @@ describe('Parse Role testing', () => {
});
it('should add multiple users to a role and remove users', (done) => {
let user, user2, user3;
let role;
let obj;
var user, user2, user3;
var role;
var obj;
const prACL = new Parse.ACL();
var prACL = new Parse.ACL();
prACL.setPublicReadAccess(true);
prACL.setPublicWriteAccess(true);
@@ -383,19 +365,19 @@ describe('Parse Role testing', () => {
return user3.save({ username: 'user3', password: 'omgbbq' });
}).then(() => {
role = new Parse.Role('sharedRole', prACL);
const users = role.relation('users');
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
const query = new Parse.Query('_Role');
var query = new Parse.Query('_Role');
query.equalTo('name', 'sharedRole');
return query.find({ useMasterKey: true });
}).then((role) => {
expect(role.length).toEqual(1);
const users = role[0].relation('users').query();
var users = role[0].relation('users').query();
return users.find({ useMasterKey: true });
}).then((users) => {
expect(users.length).toEqual(3);
@@ -409,17 +391,17 @@ describe('Parse Role testing', () => {
return obj.save(null, { sessionToken: user.getSessionToken() });
}).then(() => {
// query for saved role and get 3 users
const query = new Parse.Query('_Role');
var query = new Parse.Query('_Role');
query.equalTo('name', 'sharedRole');
return query.find({ useMasterKey: true });
}).then((role) => {
expect(role.length).toEqual(1);
const users = role[0].relation('users');
var users = role[0].relation('users');
users.remove(user);
users.remove(user3);
return role[0].save({}, { useMasterKey: true });
}).then((role) =>{
const users = role.relation('users').query();
var users = role.relation('users').query();
return users.find({ useMasterKey: true });
}).then((users) => {
expect(users.length).toEqual(1);
@@ -428,103 +410,4 @@ describe('Parse Role testing', () => {
});
});
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) => {
const user = new Parse.User();
user
.save({ username: 'admin', password: 'admin' })
.then((user) => {
const aCL = new Parse.ACL();
aCL.setPublicReadAccess(true);
aCL.setPublicWriteAccess(true);
const role = new Parse.Role('admin', aCL);
const users = role.relation('users');
users.add(user);
role
.save({}, { useMasterKey: true })
.then(() => {
const 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) => {
const user = new Parse.User();
user
.save({ username: 'admin', password: 'admin' })
.then((user) => {
const aCL = new Parse.ACL();
aCL.setPublicReadAccess(true);
aCL.setPublicWriteAccess(true);
const role = new Parse.Role('admin', aCL);
const users = role.relation('users');
users.add(user);
role
.save({}, { useMasterKey: true })
.then(() => {
const otherUser = new Parse.User();
otherUser
.save({ username: 'otherUser', password: 'otherUser' })
.then((otherUser) => {
const 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) => {
const user = new Parse.User();
user
.save({ username: 'admin', password: 'admin' })
.then((user) => {
const aCL = new Parse.ACL();
aCL.setPublicReadAccess(true);
aCL.setPublicWriteAccess(true);
const role = new Parse.Role('admin', aCL);
const users = role.relation('users');
users.add(user);
role
.save({}, { useMasterKey: true })
.then(() => {
const 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();
});
});
});
});
});
-66
View File
@@ -1,66 +0,0 @@
'use strict';
/* Tests for ParseServer.js */
const express = require('express');
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
import PostgresStorageAdapter from '../src/Adapters/Storage/Postgres/PostgresStorageAdapter';
import ParseServer from '../src/ParseServer';
describe('Server Url Checks', () => {
const app = express();
app.get('/health', function(req, res){
res.json({
status: 'ok'
});
});
app.listen(13376);
it('validate good server url', (done) => {
Parse.serverURL = 'http://localhost:13376';
ParseServer.verifyServerUrl(function(result) {
if(!result) {
done.fail('Did not pass valid url');
}
done();
});
});
it('mark bad server url', (done) => {
Parse.serverURL = 'notavalidurl';
ParseServer.verifyServerUrl(function(result) {
if(result) {
done.fail('Did not mark invalid url');
}
done();
});
});
it('handleShutdown, close connection', (done) => {
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
let databaseAdapter;
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
databaseAdapter = new PostgresStorageAdapter({
uri: process.env.PARSE_SERVER_TEST_DATABASE_URI || postgresURI,
collectionPrefix: 'test_',
});
} else {
databaseAdapter = new MongoStorageAdapter({
uri: mongoURI,
collectionPrefix: 'test_',
});
}
const newConfiguration = Object.assign({}, defaultConfiguration, { databaseAdapter });
const parseServer = ParseServer.start(newConfiguration, () => {
parseServer.handleShutdown();
parseServer.server.close((err) => {
if (err) {
done.fail('Close Server Error')
}
reconfigureServer({}).then(() => {
done();
});
});
});
});
});
+4 -25
View File
@@ -1,7 +1,5 @@
const ParseServerRESTController = require('../src/ParseServerRESTController').ParseServerRESTController;
const ParseServer = require('../src/ParseServer').default;
const Parse = require('parse/node').Parse;
let RESTController;
describe('ParseServerRESTController', () => {
@@ -105,28 +103,6 @@ describe('ParseServerRESTController', () => {
});
});
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();
@@ -135,7 +111,10 @@ describe('ParseServerRESTController', () => {
}).then(sessions => {
expect(sessions.length).toBe(0);
done();
}, done.fail);
}, (err) => {
jfail(err);
done();
});
});
it('ensures a session token is created when passing installationId != cloud', (done) => {
-126
View File
@@ -1,126 +0,0 @@
//
// Tests behavior of Parse Sessions
//
"use strict";
function setupTestUsers() {
const user1 = new Parse.User();
const user2 = new Parse.User();
const user3 = new Parse.User();
user1.set("username", "testuser_1");
user2.set("username", "testuser_2");
user3.set("username", "testuser_3");
user1.set("password", "password");
user2.set("password", "password");
user3.set("password", "password");
return user1.signUp().then(() => {
return user2.signUp();
}).then(() => {
return user3.signUp();
})
}
describe('Parse.Session', () => {
// multiple sessions with masterKey + sessionToken
it('should retain original sessionTokens with masterKey & sessionToken set', (done) => {
setupTestUsers().then((user) => {
const query = new Parse.Query(Parse.Session);
return query.find({
useMasterKey: true,
sessionToken: user.get('sessionToken')
});
}).then((results) => {
const foundKeys = [];
expect(results.length).toBe(3);
for(const key in results) {
const sessionToken = results[key].get('sessionToken');
if(foundKeys[sessionToken]) {
fail('Duplicate session token present in response');
break;
}
foundKeys[sessionToken] = 1;
}
done();
}).catch((err) => {
fail(err);
});
});
// single session returned, with just one sessionToken
it('should retain original sessionTokens with just sessionToken set', (done) => {
let knownSessionToken;
setupTestUsers().then((user) => {
knownSessionToken = user.get('sessionToken');
const query = new Parse.Query(Parse.Session);
return query.find({
sessionToken: knownSessionToken
});
}).then((results) => {
expect(results.length).toBe(1);
const sessionToken = results[0].get('sessionToken');
expect(sessionToken).toBe(knownSessionToken);
done();
}).catch((err) => {
fail(err);
});
});
// multiple users with masterKey + sessionToken
it('token on users should retain original sessionTokens with masterKey & sessionToken set', (done) => {
setupTestUsers().then((user) => {
const query = new Parse.Query(Parse.User);
return query.find({
useMasterKey: true,
sessionToken: user.get('sessionToken')
});
}).then((results) => {
const foundKeys = [];
expect(results.length).toBe(3);
for(const key in results) {
const sessionToken = results[key].get('sessionToken');
if(foundKeys[sessionToken] && sessionToken !== undefined) {
fail('Duplicate session token present in response');
break;
}
foundKeys[sessionToken] = 1;
}
done();
}).catch((err) => {
fail(err);
});
});
// multiple users with just sessionToken
it('token on users should retain original sessionTokens with just sessionToken set', (done) => {
let knownSessionToken;
setupTestUsers().then((user) => {
knownSessionToken = user.get('sessionToken');
const query = new Parse.Query(Parse.User);
return query.find({
sessionToken: knownSessionToken
});
}).then((results) => {
const foundKeys = [];
expect(results.length).toBe(3);
for(const key in results) {
const sessionToken = results[key].get('sessionToken');
if(foundKeys[sessionToken] && sessionToken !== undefined) {
fail('Duplicate session token present in response');
break;
}
foundKeys[sessionToken] = 1;
}
done();
}).catch((err) => {
fail(err);
});
});
});
+351 -1208
View File
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -1,41 +1,41 @@
const ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
var ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
describe('ParseWebSocket', function() {
it('can be initialized', function() {
const ws = {};
const parseWebSocket = new ParseWebSocket(ws);
var ws = {};
var parseWebSocket = new ParseWebSocket(ws);
expect(parseWebSocket.ws).toBe(ws);
});
it('can handle events defined in typeMap', function() {
const ws = {
var ws = {
on: jasmine.createSpy('on')
};
const callback = {};
const parseWebSocket = new ParseWebSocket(ws);
var callback = {};
var parseWebSocket = new ParseWebSocket(ws);
parseWebSocket.on('disconnect', callback);
expect(parseWebSocket.ws.on).toHaveBeenCalledWith('close', callback);
});
it('can handle events which are not defined in typeMap', function() {
const ws = {
var ws = {
on: jasmine.createSpy('on')
};
const callback = {};
const parseWebSocket = new ParseWebSocket(ws);
var callback = {};
var parseWebSocket = new ParseWebSocket(ws);
parseWebSocket.on('open', callback);
expect(parseWebSocket.ws.on).toHaveBeenCalledWith('open', callback);
});
it('can send a message', function() {
const ws = {
var ws = {
send: jasmine.createSpy('send')
};
const parseWebSocket = new ParseWebSocket(ws);
var parseWebSocket = new ParseWebSocket(ws);
parseWebSocket.send('message')
expect(parseWebSocket.ws.send).toHaveBeenCalledWith('message');
+6 -9
View File
@@ -1,11 +1,11 @@
const ParseWebSocketServer = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocketServer;
var ParseWebSocketServer = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocketServer;
describe('ParseWebSocketServer', function() {
beforeEach(function(done) {
// Mock ws server
const EventEmitter = require('events');
const mockServer = function() {
var EventEmitter = require('events');
var mockServer = function() {
return new EventEmitter();
};
jasmine.mockLibrary('ws', 'Server', mockServer);
@@ -13,11 +13,9 @@ describe('ParseWebSocketServer', function() {
});
it('can handle connect event when ws is open', function(done) {
const onConnectCallback = jasmine.createSpy('onConnectCallback');
const http = require('http');
const server = http.createServer();
const parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
const ws = {
var onConnectCallback = jasmine.createSpy('onConnectCallback');
var parseWebSocketServer = new ParseWebSocketServer({}, onConnectCallback, 5).server;
var ws = {
readyState: 0,
OPEN: 0,
ping: jasmine.createSpy('ping')
@@ -29,7 +27,6 @@ describe('ParseWebSocketServer', function() {
// Make sure we ping to the client
setTimeout(function() {
expect(ws.ping).toHaveBeenCalled();
server.close();
done();
}, 10)
});
+7 -85
View File
@@ -147,14 +147,14 @@ describe("Password Policy: ", () => {
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
validatorPattern: 1234 // number is not a valid setting
validatorPattern: "abc" // string is not a valid setting
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
fail('passwordPolicy.validatorPattern type test failed');
done();
}).catch(err => {
expect(err).toEqual('passwordPolicy.validatorPattern must be a regex string or RegExp object.');
expect(err).toEqual('passwordPolicy.validatorPattern must be a RegExp.');
done();
});
});
@@ -197,50 +197,6 @@ describe("Password Policy: ", () => {
})
});
it('signup should fail if password does not conform to the policy enforced using validatorPattern string', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
validatorPattern: "^.{8,}" // password should contain at least 8 char
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("less");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
fail('Should have failed as password does not conform to the policy.');
done();
}).catch((error) => {
expect(error.code).toEqual(142);
done();
});
})
});
it('signup should fail if password is empty', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
validatorPattern: "^.{8,}" // password should contain at least 8 char
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
fail('Should have failed as password does not conform to the policy.');
done();
}).catch((error) => {
expect(error.message).toEqual('Cannot sign up user with an empty password.');
done();
});
})
});
it('signup should succeed if password conforms to the policy enforced using validatorPattern', (done) => {
const user = new Parse.User();
reconfigureServer({
@@ -275,40 +231,6 @@ describe("Password Policy: ", () => {
})
});
it('signup should succeed if password conforms to the policy enforced using validatorPattern string', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
validatorPattern: "[!@#$]+" // password should contain at least one special char
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("p@sswrod");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.logOut().then(() => {
Parse.User.logIn("user1", "p@sswrod").then(function () {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
done();
});
}).catch((error) => {
jfail(error);
fail('logout should have succeeded');
done();
});
}).catch((error) => {
jfail(error);
fail('Signup should have succeeded as password conforms to the policy.');
done();
});
})
});
it('signup should fail if password does not conform to the policy enforced using validatorCallback', (done) => {
const user = new Parse.User();
reconfigureServer({
@@ -487,12 +409,12 @@ describe("Password Policy: ", () => {
fail("should login with new password");
done();
});
}).catch((error) => {
}).catch((error)=> {
jfail(error);
fail("Failed to POST request password reset");
done();
});
}).catch((error) => {
}).catch((error)=> {
jfail(error);
fail("Failed to get the reset link");
done();
@@ -917,7 +839,7 @@ describe("Password Policy: ", () => {
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
@@ -958,7 +880,7 @@ describe("Password Policy: ", () => {
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
@@ -1057,7 +979,7 @@ describe("Password Policy: ", () => {
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
passwordPolicy: {
maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
+47 -88
View File
@@ -1,14 +1,14 @@
'use strict';
const Config = require('../src/Config');
var Config = require('../src/Config');
describe('Pointer Permissions', () => {
beforeEach(() => {
Config.get(Parse.applicationId).database.schemaCache.clear();
new Config(Parse.applicationId).database.schemaCache.clear();
});
it('should work with find', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -47,7 +47,7 @@ describe('Pointer Permissions', () => {
it('should work with write', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -112,7 +112,7 @@ describe('Pointer Permissions', () => {
});
it('should let a proper user find', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -171,49 +171,8 @@ describe('Pointer Permissions', () => {
})
});
it('should query on pointer permission enabled column', (done) => {
const config = Config.get(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');
const obj2 = new Parse.Object('AnObject');
user.signUp().then(() => {
return user2.signUp()
}).then(() => {
Parse.User.logOut();
}).then(() => {
obj.set('owner', user);
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) => {
jfail(err);
fail('should not fail');
done();
})
});
it('should not allow creating objects', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
user.set({
username: 'user1',
@@ -239,7 +198,7 @@ describe('Pointer Permissions', () => {
});
it('should handle multiple writeUserFields', done => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -252,33 +211,33 @@ describe('Pointer Permissions', () => {
});
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();
})
.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 = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
@@ -291,7 +250,7 @@ describe('Pointer Permissions', () => {
});
it('should prevent creating pointer permission on bad field', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {owner: {type: 'String'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
@@ -304,7 +263,7 @@ describe('Pointer Permissions', () => {
});
it('should prevent creating pointer permission on bad field', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('owner', 'value');
object.save().then(() => {
@@ -329,7 +288,7 @@ describe('Pointer Permissions', () => {
The owner is another user than the ACL
*/
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -374,7 +333,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner"
ACL: logged in user has access
*/
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -419,7 +378,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner"
ACL: logged in user has access
*/
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -466,7 +425,7 @@ describe('Pointer Permissions', () => {
The owner is another user than the ACL
*/
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -511,7 +470,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner" : read
ACL: logged in user has access
*/
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -558,7 +517,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner" : read // proper owner
ACL: logged in user has not access
*/
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -597,7 +556,7 @@ describe('Pointer Permissions', () => {
});
it('should let master key find objects', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -626,7 +585,7 @@ describe('Pointer Permissions', () => {
});
it('should let master key get objects', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -657,7 +616,7 @@ describe('Pointer Permissions', () => {
it('should let master key update objects', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -684,7 +643,7 @@ describe('Pointer Permissions', () => {
});
it('should let master key delete objects', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -710,9 +669,9 @@ describe('Pointer Permissions', () => {
});
it('should fail with invalid pointer perms', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
// 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);
@@ -721,9 +680,9 @@ describe('Pointer Permissions', () => {
});
it('should fail with invalid pointer perms', (done) => {
const config = Config.get(Parse.applicationId);
const config = new Config(Parse.applicationId);
config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
// 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);
+1 -1
View File
@@ -43,7 +43,7 @@ dbOptionsTest[`${baseURI}?ssl=&binary=aa`] = {
describe('PostgresConfigParser.getDatabaseOptionsFromURI', () => {
it('creates a db options map from a query string', () => {
for (const key in dbOptionsTest) {
for (const key in dbOptionsTest) {
const result = parser.getDatabaseOptionsFromURI(key);
const testObj = dbOptionsTest[key];
-74
View File
@@ -1,74 +0,0 @@
const Parse = require('parse/node').Parse;
import PostgresStorageAdapter from '../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: {
schema: 'public'
}
};
//not exists schema
const databaseOptions2 = {
initOptions: {
schema: '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");
const 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;
const 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());
});
});
-117
View File
@@ -1,117 +0,0 @@
import PostgresStorageAdapter from '../src/Adapters/Storage/Postgres/PostgresStorageAdapter';
const databaseURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
const getColumns = (client, className) => {
return client.map('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className }, a => a.column_name);
};
const dropTable = (client, className) => {
return client.none('DROP TABLE IF EXISTS $<className:name>', { className });
}
describe_only_db('postgres')('PostgresStorageAdapter', () => {
const adapter = new PostgresStorageAdapter({ uri: databaseURI })
beforeEach(() => {
return adapter.deleteAllClasses();
});
it('schemaUpgrade, upgrade the database schema when schema changes', done => {
const client = adapter._client;
const className = '_PushStatus';
const schema = {
fields: {
"pushTime": { type: 'String' },
"source": { type: 'String' },
"query": { type: 'String' },
},
};
adapter.createTable(className, schema)
.then(() => getColumns(client, className))
.then(columns => {
expect(columns).toContain('pushTime');
expect(columns).toContain('source');
expect(columns).toContain('query');
expect(columns).not.toContain('expiration_interval');
schema.fields.expiration_interval = { type:'Number' };
return adapter.schemaUpgrade(className, schema);
})
.then(() => getColumns(client, className))
.then(columns => {
expect(columns).toContain('pushTime');
expect(columns).toContain('source');
expect(columns).toContain('query');
expect(columns).toContain('expiration_interval');
done();
})
.catch(error => done.fail(error));
});
it('schemaUpgrade, maintain correct schema', done => {
const client = adapter._client;
const className = 'Table';
const schema = {
fields: {
"columnA": { type: 'String' },
"columnB": { type: 'String' },
"columnC": { type: 'String' },
},
};
adapter.createTable(className, schema)
.then(() => getColumns(client, className))
.then(columns => {
expect(columns).toContain('columnA');
expect(columns).toContain('columnB');
expect(columns).toContain('columnC');
return adapter.schemaUpgrade(className, schema);
})
.then(() => getColumns(client, className))
.then(columns => {
expect(columns.length).toEqual(3);
expect(columns).toContain('columnA');
expect(columns).toContain('columnB');
expect(columns).toContain('columnC');
done();
})
.catch(error => done.fail(error));
});
it('Create a table without columns and upgrade with columns', done => {
const client = adapter._client;
const className = 'EmptyTable';
dropTable(client, className).then(() => adapter.createTable(className, {}))
.then(() => getColumns(client, className))
.then(columns => {
expect(columns.length).toBe(0);
const newSchema = {
fields: {
"columnA": { type: 'String' },
"columnB": { type: 'String' }
},
};
return adapter.schemaUpgrade(className, newSchema);
})
.then(() => getColumns(client, className))
.then(columns => {
expect(columns.length).toEqual(2);
expect(columns).toContain('columnA');
expect(columns).toContain('columnB');
done();
})
.catch(done);
});
});
describe_only_db('postgres')('PostgresStorageAdapter shutdown', () => {
it('handleShutdown, close connection', () => {
const adapter = new PostgresStorageAdapter({ uri: databaseURI });
expect(adapter._client.$pool.ending).toEqual(false);
adapter.handleShutdown();
expect(adapter._client.$pool.ending).toEqual(true);
});
});
+2 -2
View File
@@ -1,8 +1,8 @@
const PromiseRouter = require("../src/PromiseRouter").default;
var PromiseRouter = require("../src/PromiseRouter").default;
describe("PromiseRouter", () => {
it("should properly handle rejects", (done) => {
const router = new PromiseRouter();
var router = new PromiseRouter();
router.route("GET", "/dummy", ()=> {
return Promise.reject({
error: "an error",
+8 -52
View File
@@ -1,5 +1,5 @@
const request = require('request');
var request = require('request');
describe("public API", () => {
it("should get invalid_link.html", (done) => {
@@ -14,12 +14,12 @@ describe("public API", () => {
appName: 'unused',
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
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) => {
@@ -40,7 +40,7 @@ describe("public API", () => {
describe("public API without publicServerURL", () => {
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) => {
@@ -63,47 +63,3 @@ describe("public API without publicServerURL", () => {
});
});
});
describe("public API supplied with invalid application id", () => {
beforeEach(done => {
reconfigureServer({appName: "unused"})
.then(done, fail);
});
it("should get 403 on verify_email", (done) => {
request('http://localhost:8378/1/apps/invalid/verify_email', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(403);
done();
});
});
it("should get 403 choose_password", (done) => {
request('http://localhost:8378/1/apps/choose_password?id=invalid', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(403);
done();
});
});
it("should get 403 on get of request_password_reset", (done) => {
request('http://localhost:8378/1/apps/invalid/request_password_reset', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(403);
done();
});
});
it("should get 403 on post of request_password_reset", (done) => {
request.post('http://localhost:8378/1/apps/invalid/request_password_reset', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(403);
done();
});
});
it("should get 403 on resendVerificationEmail", (done) => {
request('http://localhost:8378/1/apps/invalid/resend_verification_email', (err, httpResponse) => {
expect(httpResponse.statusCode).toBe(403);
done();
});
});
});
+5 -5
View File
@@ -1,11 +1,11 @@
const request = require("request");
var request = require("request");
function createProduct() {
const file = new Parse.File("name", {
base64: new Buffer("download_file", "utf-8").toString("base64")
}, "text");
return file.save().then(function(){
const product = new Parse.Object("_Product");
var product = new Parse.Object("_Product");
product.set({
download: file,
icon: file,
@@ -160,7 +160,7 @@ describe("test validate_receipt endpoint", () => {
});
it("should not create a _Product", (done) => {
const product = new Parse.Object("_Product");
var product = new Parse.Object("_Product");
product.save().then(function(){
fail("Should not be able to save");
done();
@@ -171,7 +171,7 @@ describe("test validate_receipt endpoint", () => {
});
it("should be able to update a _Product", (done) => {
const query = new Parse.Query("_Product");
var query = new Parse.Query("_Product");
query.first().then(function(product) {
if (!product) {
return Promise.reject(new Error('Product should be found'));
@@ -189,7 +189,7 @@ describe("test validate_receipt endpoint", () => {
});
it("should not be able to remove a require key in a _Product", (done) => {
const query = new Parse.Query("_Product");
var query = new Parse.Query("_Product");
query.first().then(function(product){
if (!product) {
return Promise.reject(new Error('Product should be found'));
+179 -1010
View File
File diff suppressed because it is too large Load Diff

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