mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
399
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8fd670013 | ||
|
|
07ae85eef0 | ||
|
|
a5ce9fc175 | ||
|
|
a39d045c7d | ||
|
|
ba0a51ddc9 | ||
|
|
8d8a8b250e | ||
|
|
d0184f438d | ||
|
|
7ecb36e71e | ||
|
|
be9f27493d | ||
|
|
64df33157d | ||
|
|
d598d73f36 | ||
|
|
be2760f9dd | ||
|
|
c121b9787e | ||
|
|
3a17904ce8 | ||
|
|
a1554d04ab | ||
|
|
956f1df0f0 | ||
|
|
4dce3bd63c | ||
|
|
9fbb5e29e8 | ||
|
|
a660a0c25f | ||
|
|
839a117246 | ||
|
|
bc3cef2cd9 | ||
|
|
21f4411571 | ||
|
|
0fb16a7848 | ||
|
|
0f840b6bb8 | ||
|
|
019f9e55e1 | ||
|
|
121d151af9 | ||
|
|
52c4dd3704 | ||
|
|
952e64d94a | ||
|
|
1914083942 | ||
|
|
139b9e1cb3 | ||
|
|
3079270b3e | ||
|
|
17f4dcd176 | ||
|
|
5aafc93476 | ||
|
|
0bace67bb1 | ||
|
|
6df944704c | ||
|
|
540daa4c4a | ||
|
|
2e1ba66fd2 | ||
|
|
a103871cbc | ||
|
|
f8403943ab | ||
|
|
da2bfca618 | ||
|
|
1798c07874 | ||
|
|
e2d1fd3fe0 | ||
|
|
ea67d23ef4 | ||
|
|
cfd7bc0f3b | ||
|
|
c4d921e3eb | ||
|
|
8513b697e9 | ||
|
|
ff4ae55e34 | ||
|
|
5d359c3b63 | ||
|
|
6e23822f99 | ||
|
|
b45f308ff9 | ||
|
|
3ed6dd2685 | ||
|
|
2b4c7570ea | ||
|
|
c6546218f4 | ||
|
|
c2c059f625 | ||
|
|
008cb1dbfe | ||
|
|
2cec92007a | ||
|
|
47f00e606f | ||
|
|
92d51def6b | ||
|
|
7e54265f6d | ||
|
|
811d8b0c7a | ||
|
|
3c79cae1b2 | ||
|
|
e6cc8204b3 | ||
|
|
0571c6f95e | ||
|
|
58079f00f9 | ||
|
|
a26e4c7470 | ||
|
|
9d6b4f4cc4 | ||
|
|
d158023083 | ||
|
|
ad52ed64af | ||
|
|
5931aa8851 | ||
|
|
287810443b | ||
|
|
457e21d80f | ||
|
|
f42ccba5b1 | ||
|
|
d8a257d951 | ||
|
|
2f00e4afda | ||
|
|
95430bb4e8 | ||
|
|
29c2fad671 | ||
|
|
622f97064a | ||
|
|
51d2dd92cb | ||
|
|
bd6816c14c | ||
|
|
91193c77eb | ||
|
|
fedb381925 | ||
|
|
d094a70a3b | ||
|
|
31e2288c3d | ||
|
|
a48044c244 | ||
|
|
b6298feaa7 | ||
|
|
422723fa31 | ||
|
|
a0d1a3517f | ||
|
|
4509d25471 | ||
|
|
e94991b368 | ||
|
|
16954c2f74 | ||
|
|
9d79ba1ccb | ||
|
|
92fa6f2b8a | ||
|
|
f0949a1310 | ||
|
|
9256b2d7e6 | ||
|
|
8b21d5ab80 | ||
|
|
5f991e90fb | ||
|
|
4aa4ebe807 | ||
|
|
58ca561ac4 | ||
|
|
3c845878ac | ||
|
|
2f1b28c456 | ||
|
|
e3d3daa142 | ||
|
|
7d70af60ba | ||
|
|
c4fbc1f358 | ||
|
|
b98fbe495b | ||
|
|
8cb9706eaa | ||
|
|
ee23fc3983 | ||
|
|
9e2afca092 | ||
|
|
4b16a72992 | ||
|
|
e8be98ded2 | ||
|
|
9ad8697c7a | ||
|
|
6cc99aa193 | ||
|
|
31744c59ef | ||
|
|
9a6fb82b68 | ||
|
|
b692e8578d | ||
|
|
bd2ea87c1d | ||
|
|
c2abbae92d | ||
|
|
73aafa2d24 | ||
|
|
57efd89b3d | ||
|
|
38a525ba5f | ||
|
|
c99fdea6fb | ||
|
|
a380fcf2c7 | ||
|
|
d149d16fce | ||
|
|
aedaae1f23 | ||
|
|
14e2d26d12 | ||
|
|
1f11ad5d74 | ||
|
|
67a1da8f9f | ||
|
|
b5a2042d12 | ||
|
|
03b6449fe1 | ||
|
|
35d781a160 | ||
|
|
ad71dc40ca | ||
|
|
36607b244e | ||
|
|
17a2d269ef | ||
|
|
9dbb89a2e4 | ||
|
|
ab04641ed0 | ||
|
|
47e2db6c47 | ||
|
|
4e9e33cfbc | ||
|
|
691cf5988a | ||
|
|
d71683a7e2 | ||
|
|
ab5b759e26 | ||
|
|
f40019a326 | ||
|
|
391ac97494 | ||
|
|
88de01f3d8 | ||
|
|
4a724e8ab1 | ||
|
|
ad72226f42 | ||
|
|
cc97067573 | ||
|
|
b5c5d91754 | ||
|
|
10c7cb0bfa | ||
|
|
877ef78b5e | ||
|
|
81a55e655d | ||
|
|
e0be653f4c | ||
|
|
22ba39812b | ||
|
|
7b9ebc4e8e | ||
|
|
64e6f40779 | ||
|
|
8d67776c2e | ||
|
|
864d191d0c | ||
|
|
45a9d50110 | ||
|
|
4f903952bf | ||
|
|
0e64635dfb | ||
|
|
d64f100bc9 | ||
|
|
181fe34f0b | ||
|
|
4fbe3540bd | ||
|
|
c6b95482d2 | ||
|
|
bd4ca383fd | ||
|
|
f7af48db89 | ||
|
|
d2b5be20a8 | ||
|
|
69042fbf50 | ||
|
|
5e14147676 | ||
|
|
2a5c20376a | ||
|
|
a0c1974015 | ||
|
|
0f1aea1db1 | ||
|
|
5813fd0bf8 | ||
|
|
dcd8e5626a | ||
|
|
fd375cb927 | ||
|
|
907b160fc7 | ||
|
|
302a0dda73 | ||
|
|
e7a45cecb7 | ||
|
|
906fb27389 | ||
|
|
f3f81b692b | ||
|
|
52828683ba | ||
|
|
0add7c0c50 | ||
|
|
1bc74f4aa3 | ||
|
|
c2e3092385 | ||
|
|
79806c5eba | ||
|
|
48115349d4 | ||
|
|
87085b35b6 | ||
|
|
65a8a1fc56 | ||
|
|
f9ecebde3b | ||
|
|
e01b417d3f | ||
|
|
a54f4d43fa | ||
|
|
7a43faa49c | ||
|
|
abf6828a7f | ||
|
|
2436a85de6 | ||
|
|
1f9a4edacd | ||
|
|
6a2b972566 | ||
|
|
f3765e0b8e | ||
|
|
eeb3a7028d | ||
|
|
2533a8cdb3 | ||
|
|
ea94ae73f6 | ||
|
|
1ec70b299b | ||
|
|
45efe2f29c | ||
|
|
5e29336962 | ||
|
|
82e0a59139 | ||
|
|
51b519355a | ||
|
|
08e8a428b6 | ||
|
|
63bd333715 | ||
|
|
11071dd015 | ||
|
|
9b879dd371 | ||
|
|
eb1b84b556 | ||
|
|
80ec61f7ce | ||
|
|
9f1077c9d3 | ||
|
|
593a7a6264 | ||
|
|
4addf9dfa9 | ||
|
|
2547fd08ae | ||
|
|
581f919d1a | ||
|
|
0b1a61cd85 | ||
|
|
943b84b13f | ||
|
|
70f60267c5 | ||
|
|
f0f04d6351 | ||
|
|
1d8edd73c3 | ||
|
|
8d802b9461 | ||
|
|
271608bc82 | ||
|
|
5c5ef0313b | ||
|
|
29fec01a42 | ||
|
|
92b699920e | ||
|
|
9bfa0c60c4 | ||
|
|
0181fb51b3 | ||
|
|
e6006e800e | ||
|
|
a45d1848db | ||
|
|
6381e15e6d | ||
|
|
73195627c0 | ||
|
|
053bbefda9 | ||
|
|
032d5e55ca | ||
|
|
fe5947ce4c | ||
|
|
0e900cbefd | ||
|
|
41358d2226 | ||
|
|
73260897cd | ||
|
|
d3e6c0dea3 | ||
|
|
b4e27e1160 | ||
|
|
79b0c790f0 | ||
|
|
64f608ec9f | ||
|
|
67260b5fe0 | ||
|
|
411daf4d41 | ||
|
|
7a3c9cd33a | ||
|
|
193e5a4278 | ||
|
|
6ae0675010 | ||
|
|
a520ea0243 | ||
|
|
58bdeeee2d | ||
|
|
fc7f9f76f5 | ||
|
|
ef8d534679 | ||
|
|
1c9852e53f | ||
|
|
3f0de0c2ed | ||
|
|
eccdeccb59 | ||
|
|
f16f8423db | ||
|
|
bd60a5c1d1 | ||
|
|
40b9f953ca | ||
|
|
15eafd4dcc | ||
|
|
11cde12245 | ||
|
|
5861996cb0 | ||
|
|
ca70ad0141 | ||
|
|
5a22df13a5 | ||
|
|
5b7144176c | ||
|
|
4979503f63 | ||
|
|
e32a37c2ec | ||
|
|
f7f17cdb23 | ||
|
|
ecf422b2d9 | ||
|
|
49dc8df1de | ||
|
|
c3c5f06efd | ||
|
|
2a454fec35 | ||
|
|
01b4c4e0cb | ||
|
|
3714d68aaf | ||
|
|
77daa3ca18 | ||
|
|
0b574f04a9 | ||
|
|
a89a30182e | ||
|
|
96a9588a6e | ||
|
|
66cdfaed4b | ||
|
|
9262147a6f | ||
|
|
8b4e9ca637 | ||
|
|
2160d10990 | ||
|
|
e7f2995143 | ||
|
|
d5940b17b6 | ||
|
|
f864141663 | ||
|
|
711db9ccd2 | ||
|
|
6a6829ce80 | ||
|
|
d67be492f5 | ||
|
|
ea45f69af6 | ||
|
|
43c3d8e2a2 | ||
|
|
d8a1a614e8 | ||
|
|
553db0ada5 | ||
|
|
388a59d816 | ||
|
|
de4c1ee418 | ||
|
|
cb15c0b616 | ||
|
|
8dd864a033 | ||
|
|
c26db2248f | ||
|
|
b2a3479dbf | ||
|
|
deedf7b370 | ||
|
|
5f849ca662 | ||
|
|
0279b5e5f3 | ||
|
|
4cb6e7d209 | ||
|
|
56bb505df2 | ||
|
|
ceb5a1b5f0 | ||
|
|
0958b15297 | ||
|
|
5d9dbea07b | ||
|
|
f331f6644c | ||
|
|
df029b82eb | ||
|
|
edba550cf6 | ||
|
|
c7775eb0b9 | ||
|
|
0b44820a7d | ||
|
|
b7322064d8 | ||
|
|
ac3f5cd686 | ||
|
|
9fd34fd25e | ||
|
|
7ff3a3813d | ||
|
|
754f5d0505 | ||
|
|
0e8c3c2457 | ||
|
|
423a50133a | ||
|
|
a3d416de4a | ||
|
|
a2dfcc8da8 | ||
|
|
f4734a65c0 | ||
|
|
aa071e034e | ||
|
|
4f46eceacf | ||
|
|
81ebbb8954 | ||
|
|
e82395e268 | ||
|
|
0159360a5d | ||
|
|
aab10bea79 | ||
|
|
752f0e9143 | ||
|
|
025e7a3218 | ||
|
|
ca286b7108 | ||
|
|
a6c988176e | ||
|
|
e069b20ef9 | ||
|
|
ae30ec647b | ||
|
|
fc53fa9c32 | ||
|
|
c1dcaf1271 | ||
|
|
a9067260fc | ||
|
|
2e569ca7dd | ||
|
|
8111c3338e | ||
|
|
fb36dfa26f | ||
|
|
998b271994 | ||
|
|
a7821f6e57 | ||
|
|
ddb6f7bc57 | ||
|
|
8124bf4600 | ||
|
|
df74a1f8ae | ||
|
|
e0704b440c | ||
|
|
01b05b060f | ||
|
|
a270632570 | ||
|
|
b9afccd338 | ||
|
|
37daafc7b0 | ||
|
|
8359284329 | ||
|
|
2b0c39caaa | ||
|
|
11f7712651 | ||
|
|
7292fa7f11 | ||
|
|
4d17bed374 | ||
|
|
143bd11638 | ||
|
|
bd1689190f | ||
|
|
08bac9f790 | ||
|
|
f2df0303b2 | ||
|
|
fbb2b086af | ||
|
|
127629d4f7 | ||
|
|
6676c6a4b9 | ||
|
|
b6fb3d8d51 | ||
|
|
6264f99ad5 | ||
|
|
ee9099d6ad | ||
|
|
c83a787510 | ||
|
|
85567310d4 | ||
|
|
8f1c1f419b | ||
|
|
d800ff85e2 | ||
|
|
8c2c76dd26 | ||
|
|
6e2fba4ae4 | ||
|
|
e4a7fbbf98 | ||
|
|
edb7b70ced | ||
|
|
11c6170ed1 | ||
|
|
d137e27eb9 | ||
|
|
a28257b2b7 | ||
|
|
f22cea5ca9 | ||
|
|
0bc53753b0 | ||
|
|
1eff210a51 | ||
|
|
4ea455b20a | ||
|
|
e9e6a4764e | ||
|
|
4785891e99 | ||
|
|
8e1e89b794 | ||
|
|
cf6ce5b9a3 | ||
|
|
6be9ee5491 | ||
|
|
fe5f0a8980 | ||
|
|
303851d86d | ||
|
|
0cfae4d603 | ||
|
|
7e0e48457b | ||
|
|
7b5f89e037 | ||
|
|
7bc83c372d | ||
|
|
28c1824394 | ||
|
|
19271fa1ef | ||
|
|
b80d8a0d3d | ||
|
|
e9dfb68a37 | ||
|
|
fbbc23772b | ||
|
|
5082609937 | ||
|
|
9abf17730f | ||
|
|
bb9ce773bb | ||
|
|
1029ddf563 | ||
|
|
71fb6cef5e | ||
|
|
2fb4f89324 | ||
|
|
1ae3613608 | ||
|
|
5f335a9d30 |
@@ -4,6 +4,11 @@
|
||||
],
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-0"
|
||||
"stage-3",
|
||||
["env", {
|
||||
"targets": {
|
||||
"node": "4.6"
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
*.md
|
||||
PATENTS
|
||||
LICENSE
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.gitignore
|
||||
.travis.yml
|
||||
.istanbul.yml
|
||||
.git
|
||||
.github
|
||||
|
||||
# Build folder
|
||||
lib/
|
||||
|
||||
# Tests
|
||||
spec/
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
@@ -0,0 +1,3 @@
|
||||
lib
|
||||
coverage
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"flowtype"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 2],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"no-trailing-spaces": 2,
|
||||
"eol-last": 2,
|
||||
"space-in-parens": ["error", "never"],
|
||||
"no-multiple-empty-lines": 1,
|
||||
"prefer-const": "error",
|
||||
"space-infix-ops": "error",
|
||||
"no-useless-escape": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,28 @@
|
||||
Please read the following instructions carefully.
|
||||
We use GitHub Issues for bugs.
|
||||
|
||||
Check out https://github.com/ParsePlatform/parse-server/issues/1271 for an ideal bug report.
|
||||
The closer your issue report is to that one, the more likely we are to be able to help, and the more likely we will be to fix the issue quickly!
|
||||
If you have a non-bug question, ask on Stack Overflow or Server Fault:
|
||||
- https://stackoverflow.com/questions/tagged/parse.com
|
||||
- https://serverfault.com/tags/parse
|
||||
|
||||
Many members of the community use Stack Overflow and Server Fault to ask questions.
|
||||
Read through the existing questions or ask your own!
|
||||
- Stack Overflow: http://stackoverflow.com/questions/tagged/parse.com
|
||||
- Server Fault: https://serverfault.com/tags/parse
|
||||
You may also search through existing issues before opening a new one: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
For database migration help, please file a bug report at https://parse.com/help#report
|
||||
|
||||
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server!
|
||||
|
||||
- [ ] You've met the prerequisites: https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites.
|
||||
|
||||
- [ ] You're running the latest version of Parse Server: https://github.com/ParsePlatform/parse-server/releases
|
||||
|
||||
- [ ] You've searched through existing issues: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue Chances are that your issue has been reported or resolved before.
|
||||
|
||||
- [ ] You have filled out every section below. Issues without sufficient information are more likely to be closed.
|
||||
|
||||
--
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
|
||||
### Issue Description
|
||||
|
||||
[DELETE EVERYTHING ABOVE THIS LINE BEFORE SUBMITTING YOUR ISSUE]
|
||||
|
||||
Describe your issue in as much detail as possible.
|
||||
|
||||
[FILL THIS OUT]
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
|
||||
|
||||
1. [FILL THIS OUT]
|
||||
2. [FILL THIS OUT]
|
||||
3. [FILL THIS OUT]
|
||||
|
||||
#### Expected Results
|
||||
|
||||
[FILL THIS OUT]
|
||||
What you expected to happen.
|
||||
|
||||
#### Actual Outcome
|
||||
|
||||
[FILL THIS OUT]
|
||||
What is happening instead.
|
||||
|
||||
### Environment Setup
|
||||
|
||||
@@ -62,6 +40,4 @@ Please include a detailed list of steps that reproduce the issue. Include curl c
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
You can turn on additional logging by configuring VERBOSE=1 in your environment.
|
||||
|
||||
[FILL THIS OUT]
|
||||
Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment.
|
||||
|
||||
@@ -13,6 +13,7 @@ lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
@@ -44,6 +45,7 @@ lib/
|
||||
|
||||
# cache folder
|
||||
.cache
|
||||
.eslintcache
|
||||
|
||||
# Mac DS_Store files
|
||||
.DS_Store
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
instrumentation:
|
||||
excludes: ["**/spec/**"]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
],
|
||||
"exclude": [
|
||||
"**/spec/**",
|
||||
"lib/"
|
||||
]
|
||||
}
|
||||
|
||||
+52
-43
@@ -1,61 +1,70 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
sudo: required
|
||||
node_js:
|
||||
- '4.5'
|
||||
- '6.1'
|
||||
services:
|
||||
- mongodb
|
||||
- postgresql
|
||||
- redis-server
|
||||
- docker
|
||||
addons:
|
||||
postgresql: '9.5'
|
||||
before_script:
|
||||
- ls -al "$HOME/.mongodb/versions"
|
||||
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
|
||||
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/istanbul cover'
|
||||
matrix:
|
||||
- MONGODB_VERSION=3.0.8
|
||||
- MONGODB_VERSION=3.2.6
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: PARSE_SERVER_TEST_DB=postgres
|
||||
apt_packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^[0-9]+.[0-9]+.[0-9]+(-.*)?$/
|
||||
- /^greenkeeper/.*$/
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- "$HOME/.mongodb/versions"
|
||||
|
||||
# Test stage
|
||||
stage: test
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/nyc'
|
||||
- NODE_VERSION=6.10
|
||||
matrix:
|
||||
- MONGODB_VERSION=3.2.13
|
||||
- MONGODB_VERSION=3.4.4
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
- NODE_VERSION=8.1.3
|
||||
before_install:
|
||||
- nvm install $NODE_VERSION
|
||||
- nvm use $NODE_VERSION
|
||||
before_script:
|
||||
- node -e 'require("./lib/index.js")'
|
||||
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
|
||||
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- silent=1 mongodb-runner --start
|
||||
after_script:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
after_failure:
|
||||
- (cd resources; python travis_after_all.py)
|
||||
after_success:
|
||||
- (cd resources; python travis_after_all.py)
|
||||
- export $(cat resources/.to_export_back)
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
on:
|
||||
branch: master
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
- provider: npm
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$MONGODB_VERSION = '3.0.8'"
|
||||
repo: ParsePlatform/parse-server
|
||||
jobs:
|
||||
include:
|
||||
# release on github latest branch
|
||||
- stage: release
|
||||
node_js: '4.6'
|
||||
env:
|
||||
before_script: skip
|
||||
after_script: skip
|
||||
script: skip
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
on:
|
||||
branch: master
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email:
|
||||
secure: "YweTGc22uqFWpzbfiUa5ptBLRAy6tt6d9TZLwEkPtmnsWzN9dguGyKWmXiw0qL+848FWQ5PWzUgBn5XdigV9tF3rJY6RGs8i38WulNjwSoGuRZa3AChsQHAb1KenANcJybzhnwgEj9gRsrGZPEsyI2whfake/xLDtG91kHjpJANsd4gseOh6fdS4FIYCbyXvSbC7S0yZzIJkgSkMqJO7RJ8r0HYQ7srYIw31dM3ZXSmUYu+GaMDUUu3RLAGYoKaROxhDRnzkjdeLLiwZH8hQ/6CaqwqX54iJ6OS+MOQU3fi5ZXalA7RZvtC4RmrcCkaTf3i0f+5xejYIFgzXdYGMVm8DUc82tDw1s4b6Pb19bgi1xXOQ0IKzRmZuGxnvkRN61dnYdnpnnNuG97HXgVjiOigZXVLZkWazUdnf9zXqmkC+KxPfa4Ldqg0TMjQ9J14n6TXxRti8Tt0xMa1Uzho7VdsxdJy35Bghy398O6X8VdX6parfzEkX7c/JzcA3TIMJ9+S1dy3J4Tb2URB5367W6h7cDeXtGmwLTFu75Q1CNqRJkUGbSrX2NyMqG5tc8oaTM+OWvLxcbKPRy9T6qN7x2JmCHHaapf8/8VR9wCy2PwE5j+KWhmctEHqqOgrtG5gsjC0eCPJsposxZWyM7M6aUpXe3w+olBfKq9apUGStUSU="
|
||||
api_key:
|
||||
secure: "QprkaqQ+WCvZQR4qIEs5iS6peMCbRd8Hgt0s6HfdmhavNXwDFY8Bkdf6zJwWHLiqs4pyClXDZ2f6QiOs7y9IvJZ+wOIbsf4N5V6s06zOxJ0NAOwhe0mgWS3Us0zgXIfmW4BpmGnU4ql/qGL+9vNfyQJ7wxEJxVK7hiYh9Epu49E2jmefDqTX+SNSrDCg4HkRcxerxYGnAJDCP50QaNlyLSciODD6wHaddrSYkdvmISLMnLHug61OkE4OBIOWXfYV+e31kDj+zgczAfVQgekDKtaimCQclHFrmaEPl0KIm6wsDQAw5HWkepA/WZfv4SbCrDaKJDZw+LBI7dR0ezmiOH/zmWWrRW7D4wjkDGiumWjA8etuf8I4GRyC/d1RS+hnlvPr0Bu+WljuVxLoK3nhZOdiK5t4QlVDoGankkRjLylwFQgo8tzu5N0dc26z3ClowTwcKsjStmFr53gjCD7l3qoFjyPot1JlW3LPhG9Nch7rK33/7ONqVai3zxb1xB9ynd8TSzKi4/66LeYEDcGVM5A9Vmkp+egCnOhkOAXdI8O4jid98NANir+U4xmUYZ2PAMNmSeHlpSpwH2pC1/BHpDKA9RZEuEdr3sgmtuXXwwOCp+xvuVwxZZ6+gVEjG4nGTqSNxUIq1fzjGih8ElJtbM1Uhh2dVE9uxW7EC/oCuuk="
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
repo: parse-community/parse-server
|
||||
|
||||
+411
-3
@@ -1,17 +1,425 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.2.25-beta.1
|
||||
### master
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.2...master)
|
||||
|
||||
### 2.6.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.1...2.6.2)
|
||||
|
||||
#### Improvements:
|
||||
* PushWorker/PushQueue channels are properly prefixed with the Parse applicationId, thanks to [Marvel Mathew](https://github.com/marvelm)
|
||||
* You can use Parse.Cloud.afterSave hooks on _PushStatus
|
||||
* You can use Parse.Cloud.onLiveQueryEvent to track the number of clients and subscriptions
|
||||
* Adds support for more fields from the Audience class.
|
||||
|
||||
#### New Features:
|
||||
* Push: Adds ability to track sentPerUTC offset if your push scheduler supports it.
|
||||
* Push: Adds support for cleaning up invalid deviceTokens from _Installation (PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS=1).
|
||||
|
||||
#### Dependency Updates:
|
||||
* [ws@3.2.0](https://www.npmjs.com/package/ws)
|
||||
* [pg-promise@6.5.3](https://www.npmjs.com/package/pg-promise)
|
||||
* [winston-daily-rotate-file@1.5.0](https://www.npmjs.com/package/winston-daily-rotate-file)
|
||||
* [body-parser@1.18.1](https://www.npmjs.com/package/body-parser)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [nodemon@1.12.1](https://www.npmjs.com/package/nodemon)
|
||||
* [mongodb-runner@3.6.0](https://www.npmjs.com/package/mongodb-runner)
|
||||
* [babel-eslint@8.0.0](https://www.npmjs.com/package/babel-eslint)
|
||||
|
||||
### 2.6.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.0...2.6.1)
|
||||
|
||||
#### Improvements:
|
||||
* Improves overall performance of the server, more particularly with large query results.
|
||||
* Improves performance of InMemoryCacheAdapter by removing serialization.
|
||||
* Improves logging performance by skipping necessary log calls.
|
||||
* Refactors object routers to simplify logic.
|
||||
* Adds automatic indexing on $text indexes, thanks to [Diamon Lewis](https://github.com/dplewis)
|
||||
|
||||
#### New Features:
|
||||
* Push: Adds ability to send localized pushes according to the _Installation localeIdentifier
|
||||
* Push: proper support for scheduling push in user's locale time, thanks to [Marvel Mathew](https://github.com/marvelm)
|
||||
* LiveQuery: Adds ability to use LiveQuery with a masterKey, thanks to [Jeremy May](https://github.com/kenishi)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes an issue that would duplicate Session objects per userId-installationId pair.
|
||||
* Fixes an issue affecting pointer permissions introduced in this release.
|
||||
* Fixes an issue that would prevent displaying audiences correctly in dashboard.
|
||||
* Fixes an issue affecting preventLoginWithUnverifiedEmail upon signups.
|
||||
|
||||
#### Dependency Updates:
|
||||
* [pg-promise@6.3.2](https://www.npmjs.com/package/pg-promise)
|
||||
* [body-parser@1.18.0](https://www.npmjs.com/package/body-parser)
|
||||
* [nodemon@1.11.1](https://www.npmjs.com/package/nodemon)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [babel-cli@6.26.0](https://www.npmjs.com/package/babel-cli)
|
||||
|
||||
### 2.6.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.3...2.6.0)
|
||||
|
||||
#### Breaking Changes:
|
||||
* [parse-server-s3-adapter@1.2.0](https://www.npmjs.com/package/parse-server-s3-adapter): A new deprecation notice is introduced with parse-server-s3-adapter's version 1.2.0. An upcoming release will remove passing key and password arguments. AWS credentials should be set using AWS best practices. See the [Deprecation Notice for AWS credentials]( https://github.com/parse-server-modules/parse-server-s3-adapter/blob/master/README.md#deprecation-notice----aws-credentials) section of the adapter's README.
|
||||
|
||||
#### New Features
|
||||
* Polygon is fully supported as a type, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Query supports PolygonContains, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Improvements
|
||||
* Postgres: Adds support nested contains and containedIn, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Postgres: Adds support for `null` in containsAll queries, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Cloud Code: Request headers are passed to the cloud functions, thanks to [miguel-s](https://github.com/miguel-s)
|
||||
* Push: All push queries now filter only where deviceToken exists
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes issue affecting updates of _User objects when authData was passed.
|
||||
* Push: Pushing to an empty audience should now properly report a failed _PushStatus
|
||||
* Linking Users: Fixes issue affecting linking users with sessionToken only
|
||||
|
||||
#### Dependency Updates:
|
||||
* [ws@3.1.0](https://www.npmjs.com/package/ws)
|
||||
* [mime@1.4.0](https://www.npmjs.com/package/mime)
|
||||
* [semver@5.4.0](https://www.npmjs.com/package/semver)
|
||||
* [uws@8.14.1](https://www.npmjs.com/package/uws)
|
||||
* [bcrypt@1.0.3](https://www.npmjs.com/package/bcrypt)
|
||||
* [mongodb@2.2.31](https://www.npmjs.com/package/mongodb)
|
||||
* [redis@2.8.0](https://www.npmjs.com/package/redis)
|
||||
* [pg-promise@6.3.1](https://www.npmjs.com/package/pg-promise)
|
||||
* [commander@2.11.0](https://www.npmjs.com/package/commander)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [jasmine@2.8.0](https://www.npmjs.com/package/jasmine)
|
||||
* [babel-register@6.26.0](https://www.npmjs.com/package/babel-register)
|
||||
* [babel-core@6.26.0](https://www.npmjs.com/package/babel-core)
|
||||
* [cross-env@5.0.2](https://www.npmjs.com/package/cross-env)
|
||||
|
||||
### 2.5.3
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.2...2.5.3)
|
||||
|
||||
#### New Features:
|
||||
* badge property on android installations will now be set as on iOS (#3970), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes incorrect number parser for cache options
|
||||
|
||||
### 2.5.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.1...2.5.2)
|
||||
|
||||
#### Improvements:
|
||||
* Restores ability to run on node >= 4.6
|
||||
* Adds ability to configure cache from CLI
|
||||
* Removes runtime check for node >= 4.6
|
||||
|
||||
### 2.5.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.0...2.5.1)
|
||||
|
||||
#### New Features:
|
||||
* Adds ability to set default objectId size (#3950), thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
|
||||
#### Improvements:
|
||||
* Uses LRU cache instead of InMemoryCache by default (#3979), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* iOS pushes are now using HTTP/2.0 instead of binary API (#3983), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [parse@1.10.0](https://www.npmjs.com/package/parse)
|
||||
* [pg-promise@6.3.0](https://www.npmjs.com/package/pg-promise)
|
||||
* [parse-server-s3-adapter@1.1.0](https://www.npmjs.com/package/parse-server-s3-adapter)
|
||||
* [parse-server-push-adapter@2.0.0](https://www.npmjs.com/package/parse-server-push-adapter)
|
||||
|
||||
### 2.5.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.4.2...2.5.0)
|
||||
|
||||
#### New Features:
|
||||
* Adds ability to run full text search (#3904), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Adds ability to run `$withinPolygon` queries (#3889), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Adds ability to pass read preference per query with mongodb (#3865), thanks to [davimacedo](https://github.com/davimacedo)
|
||||
* beforeFind trigger now includes `isGet` for get queries (#3862), thanks to [davimacedo](https://github.com/davimacedo)
|
||||
* Adds endpoints for dashboard's audience API (#3861), thanks to [davimacedo](https://github.com/davimacedo)
|
||||
* Restores the job scheduling endpoints (#3927), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Improvements:
|
||||
* Removes unnecessary warning when using maxTimeMs with mongodb, thanks to [Tyler Brock](https://github.com/tbrock)
|
||||
* Improves access control on system classes (#3916), thanks to [Worathiti Manosroi](https://github.com/pungme)
|
||||
* Adds bytes support in postgres (#3894), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes issue with vkontakte adapter that would hang the request, thanks to [Denis Trofimov](https://github.com/denistrofimov)
|
||||
* Fixes issue affecting null relational data (#3924), thanks to [davimacedo](https://github.com/davimacedo)
|
||||
* Fixes issue affecting session token deletion (#3937), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fixes issue affecting the serverInfo endpoint (#3933), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fixes issue affecting beforeSave with dot-noted sub-documents (#3912), thanks to [IlyaDiallo](https://github.com/IlyaDiallo)
|
||||
* Fixes issue affecting emails being sent when using a 3rd party auth (#3882), thanks to [davimacedo](https://github.com/davimacedo)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [commander@2.10.0](https://www.npmjs.com/package/commander)
|
||||
* [pg-promise@5.9.7](https://www.npmjs.com/package/pg-promise)
|
||||
* [lru-cache@4.1.0](https://www.npmjs.com/package/lru-cache)
|
||||
* [mongodb@2.2.28](https://www.npmjs.com/package/mongodb)
|
||||
|
||||
##### Devevelopment dependencies
|
||||
* [babel-core@6.25.0](https://www.npmjs.com/package/babel-core)
|
||||
* [cross-env@5.0.1](https://www.npmjs.com/package/cross-env)
|
||||
* [nyc@11.0.2](https://www.npmjs.com/package/nyc)
|
||||
|
||||
### 2.4.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.4.1...2.4.2)
|
||||
|
||||
#### New Features:
|
||||
* ParseQuery: Support for withinPolygon [#3866](https://github.com/parse-community/parse-server/pull/3866), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Improvements:
|
||||
* Postgres: Use transactions when deleting a class, [#3869](https://github.com/parse-community/parse-server/pull/3836), thanks to [Vitaly Tomilov](https://github.com/vitaly-t)
|
||||
* Postgres: Proper support for GeoPoint equality query, [#3874](https://github.com/parse-community/parse-server/pull/3836), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* beforeSave and liveQuery will be correctly triggered on email verification [#3851](https://github.com/parse-community/parse-server/pull/3851), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug fixes:
|
||||
* Skip authData validation if it hasn't changed, on PUT requests [#3872](https://github.com/parse-community/parse-server/pull/3872), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [mongodb@2.2.27](https://www.npmjs.com/package/mongodb)
|
||||
* [pg-promise@5.7.2](https://www.npmjs.com/package/pg-promise)
|
||||
|
||||
|
||||
### 2.4.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.4.0...2.4.1)
|
||||
|
||||
#### Bug fixes:
|
||||
* Fixes issue affecting relation updates ([#3835](https://github.com/parse-community/parse-server/pull/3835), [#3836](https://github.com/parse-community/parse-server/pull/3836)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fixes issue affecting sending push notifications, thanks to [Felipe Andrade](https://github.com/felipemobile)
|
||||
* Session are always cleared when updating the passwords ([#3289](https://github.com/parse-community/parse-server/pull/3289), [#3821](https://github.com/parse-community/parse-server/pull/3821), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [body-parser@1.17.2](https://www.npmjs.com/package/body-parser)
|
||||
* [pg-promise@5.7.1](https://www.npmjs.com/package/pg-promise)
|
||||
* [ws@3.0.0](https://www.npmjs.com/package/ws)
|
||||
|
||||
|
||||
### 2.4.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.8...2.4.0)
|
||||
|
||||
Starting 2.4.0, parse-server is tested against node 6.10 and 7.10, mongodb 3.2 and 3.4.
|
||||
If you experience issues with older versions, please [open a issue](https://github.com/parse-community/parse-server/issues).
|
||||
|
||||
#### New Features:
|
||||
* Adds `count` Class Level Permission ([#3814](https://github.com/parse-community/parse-server/pull/3814)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Proper graceful shutdown support ([#3786](https://github.com/parse-community/parse-server/pull/3786)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Let parse-server store as `scheduled` Push Notifications with push_time (#3717, #3722), thanks to [Felipe Andrade](https://github.com/felipemobile)
|
||||
|
||||
#### Improvements
|
||||
* Parse-Server images are built through docker hub, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Skip authData validation if it hasn't changed, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* [postgres] Improve performance when adding many new fields to the Schema ([#3740](https://github.com/parse-community/parse-server/pull/3740)), thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
|
||||
* Test maintenance, wordsmithing and nits ([#3744](https://github.com/parse-community/parse-server/pull/3744)), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Bug Fixes:
|
||||
* [postgres] Fixes issue affecting deleting multiple fields of a Schema ([#3734](https://github.com/parse-community/parse-server/pull/3734), [#3735](https://github.com/parse-community/parse-server/pull/3735)), thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
|
||||
* Fix issue affecting _PushStatus state ([#3808](https://github.com/parse-community/parse-server/pull/3808)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* requiresAuthentication Class Level Permission behaves correctly, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Email Verification related fields are not exposed ([#3681](https://github.com/parse-community/parse-server/pull/3681), [#3393](https://github.com/parse-community/parse-server/pull/3393), [#3432](https://github.com/parse-community/parse-server/pull/3432)), thanks to [Anthony Mosca](https://github.com/aontas)
|
||||
* HTTP query parameters are properly obfuscated in logs ([#3793](https://github.com/parse-community/parse-server/pull/3793), [#3789](https://github.com/parse-community/parse-server/pull/3789)), thanks to [@youngerong](https://github.com/youngerong)
|
||||
* Improve handling of `$near` operators in `$or` queries ([#3767](https://github.com/parse-community/parse-server/pull/3767), [#3798](https://github.com/parse-community/parse-server/pull/3798)), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
|
||||
* Fix issue affecting arrays of pointers ([#3169](https://github.com/parse-community/parse-server/pull/3169)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix issue affecting overloaded query constraints ([#3723](https://github.com/parse-community/parse-server/pull/3723), [#3678](https://github.com/parse-community/parse-server/pull/3678)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Properly catch unhandled rejections in _Installation updates ([#3795](https://github.com/parse-community/parse-server/pull/3795)), thanks to [kahoona77](https://github.com/kahoona77)
|
||||
|
||||
#### Dependency Updates:
|
||||
|
||||
* [uws@0.14.5](https://www.npmjs.com/package/uws)
|
||||
* [mime@1.3.6](https://www.npmjs.com/package/mime)
|
||||
* [mongodb@2.2.26](https://www.npmjs.com/package/mongodb)
|
||||
* [pg-promise@5.7.0](https://www.npmjs.com/package/pg-promise)
|
||||
* [semver@5.3.0](https://www.npmjs.com/package/semver)
|
||||
|
||||
##### Devevelopment dependencies
|
||||
* [babel-cli@6.24.1](https://www.npmjs.com/package/babel-cli)
|
||||
* [babel-core@6.24.1](https://www.npmjs.com/package/babel-core)
|
||||
* [babel-preset-es2015@6.24.1](https://www.npmjs.com/package/babel-preset-es2015)
|
||||
* [babel-preset-stage-0@6.24.1](https://www.npmjs.com/package/babel-preset-stage-0)
|
||||
* [babel-register@6.24.1](https://www.npmjs.com/package/babel-register)
|
||||
* [cross-env@5.0.0](https://www.npmjs.com/package/cross-env)
|
||||
* [deep-diff@0.3.8](https://www.npmjs.com/package/deep-diff)
|
||||
* [gaze@1.1.2](https://www.npmjs.com/package/gaze)
|
||||
* [jasmine@2.6.0](https://www.npmjs.com/package/jasmine)
|
||||
* [jasmine-spec-reporter@4.1.0](https://www.npmjs.com/package/jasmine-spec-reporter)
|
||||
* [mongodb-runner@3.5.0](https://www.npmjs.com/package/mongodb-runner)
|
||||
* [nyc@10.3.2](https://www.npmjs.com/package/nyc)
|
||||
* [request-promise@4.2.1](https://www.npmjs.com/package/request-promise)
|
||||
|
||||
|
||||
### 2.3.8
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.7...2.3.8)
|
||||
|
||||
#### New Features
|
||||
* Support for PG-Promise options, thanks to [ren dong](https://github.com/rendongsc)
|
||||
|
||||
#### Improvements
|
||||
* Improves support for graceful shutdown, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Improves configuration validation for Twitter Authentication, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
|
||||
#### Bug Fixes
|
||||
* Fixes issue affecting GeoPoint __type with Postgres, thanks to [zhoul-HS](https://github.com/zhoul-HS)
|
||||
* Prevent user creation if username or password is empty, thanks to [Wissam Abirached](https://github.com/wabirached)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [cross-env@4.0.0 ](https://www.npmjs.com/package/cross-env)
|
||||
* [ws@2.2.3](https://www.npmjs.com/package/ws)
|
||||
* [babel-core@6.24.0](https://www.npmjs.com/package/babel-core)
|
||||
* [uws@0.14.0](https://www.npmjs.com/package/uws)
|
||||
* [babel-preset-es2015@6.24.0](https://www.npmjs.com/package/babel-preset-es2015)
|
||||
* [babel-plugin-syntax-flow@6.18.0](https://www.npmjs.com/package/babel-plugin-syntax-flow)
|
||||
* [babel-cli@6.24.0](https://www.npmjs.com/package/babel-cli)
|
||||
* [babel-register@6.24.0](https://www.npmjs.com/package/babel-register)
|
||||
* [winston-daily-rotate-file@1.4.6](https://www.npmjs.com/package/winston-daily-rotate-file)
|
||||
* [mongodb@2.2.25](https://www.npmjs.com/package/mongodb)
|
||||
* [redis@2.7.0](https://www.npmjs.com/package/redis)
|
||||
* [pg-promise@5.6.4](https://www.npmjs.com/package/pg-promise)
|
||||
* [parse-server-push-adapter@1.3.0](https://www.npmjs.com/package/parse-server-push-adapter)
|
||||
|
||||
### 2.3.7
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.6...2.3.7)
|
||||
|
||||
#### New Features
|
||||
* New endpoint to resend verification email, thanks to [Xy Ziemba](https://github.com/xyziemba)
|
||||
|
||||
#### Improvements
|
||||
* Add TTL option for Redis Cache Adapter, thanks to [Ryan Foster](https://github.com/f0ster)
|
||||
* Update Postgres Storage Adapter, thanks to [Vitaly Tomilov](https://github.com/vitaly-t)
|
||||
|
||||
#### Bug Fixes
|
||||
* Add index on Role.name, fixes (#3579), thanks to [Natan Rolnik](https://github.com/natanrolnik)
|
||||
* Fix default value of userSensitiveFields, fixes (#3593), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [body-parser@1.17.1](https://www.npmjs.com/package/body-parser)
|
||||
* [express@4.15.2](https://www.npmjs.com/package/express)
|
||||
* [request@2.81.0](https://www.npmjs.com/package/request)
|
||||
* [winston-daily-rotate-file@1.4.5](https://www.npmjs.com/package/winston-daily-rotate-file)
|
||||
* [ws@2.2.0](https://www.npmjs.com/package/ws)
|
||||
|
||||
|
||||
### 2.3.6
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.5...2.3.6)
|
||||
|
||||
#### Improvements
|
||||
* Adds support for injecting a middleware for instumentation in the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Alleviate mongodb bug with $or queries [SERVER-13732](https://jira.mongodb.org/browse/SERVER-13732), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
|
||||
|
||||
#### Bug Fixes
|
||||
* Fix issue affecting password policy and empty passwords, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* Fix issue when logging url in non string objects, thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
|
||||
|
||||
#### Dependencies updates:
|
||||
* [ws@2.1.0](https://npmjs.com/package/ws)
|
||||
* [uws@0.13.0](https://npmjs.com/package/uws)
|
||||
* [pg-promise@5.6.2](https://npmjs.com/package/pg-promise)
|
||||
|
||||
|
||||
### 2.3.5
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.5)
|
||||
|
||||
#### Bug Fixes
|
||||
* Allow empty client key
|
||||
(#3497), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Fix LiveQuery unsafe user
|
||||
(#3525), thanks to [David Starke](https://github.com/dstarke)
|
||||
* Use `flushdb` instead of `flushall` in RedisCacheAdapter
|
||||
(#3523), thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Fix saving GeoPoints and Files in `_GlobalConfig` (Make sure we don't treat
|
||||
dot notation keys as topLevel atoms)
|
||||
(#3531), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.3.3
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.2...2.3.3)
|
||||
|
||||
#### Breaking Changes
|
||||
* **Minimum Node engine bumped to 4.6** (#3480), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug Fixes
|
||||
* Add logging on failure to create file (#3424), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Log Parse Errors so they are intelligible (#3431), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* MongoDB $or Queries avoid SERVER-13732 bug (#3476), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
|
||||
* Mongo object to Parse object date serialization - avoid re-serialization of iso of type Date (#3389), thanks to [nodechefMatt](https://github.com/nodechefMatt)
|
||||
|
||||
#### Improvements
|
||||
* Ground preparations for push scalability (#3080), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Use uWS as optional dependency for ws server (#3231), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.3.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.1...2.3.2)
|
||||
|
||||
#### New features
|
||||
* Add parseFrameURL for masking user-facing pages (#3267), thanks to [Lenart Rudel](https://github.com/lenart)
|
||||
|
||||
#### Bug fixes
|
||||
* Fix Parse-Server to work with winston-daily-rotate-1.4.2 (#3335), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Improvements
|
||||
* Add support for regex string for password policy validatorPattern setting (#3331), thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* LiveQuery should match subobjects with dot notation (#3322), thanks to [David Starke](https://github.com/dstarke)
|
||||
* Reduce time to process high number of installations for push (#3264), thanks to [jeacott1](https://github.com/jeacott1)
|
||||
* Fix trivial typo in error message (#3238), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 2.3.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.0...2.3.1)
|
||||
|
||||
A major issue was introduced when refactoring the authentication modules.
|
||||
This release addresses only that issue.
|
||||
|
||||
### 2.3.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.2.25...2.3.0)
|
||||
|
||||
#### Breaking changes
|
||||
* Parse.Cloud.useMasterKey() is a no-op, please refer to (Cloud Code migration guide)[https://github.com/ParsePlatform/parse-server/wiki/Compatibility-with-Hosted-Parse#cloud-code]
|
||||
* Authentication helpers are now proper adapters, deprecates oauth option in favor of auth.
|
||||
* DEPRECATES: facebookAppIds, use `auth: { facebook: { appIds: ["AAAAAAAAA" ] } }`
|
||||
* `email` field is not returned anymore for `Parse.User` queries. (Provided only on the user itself if provided).
|
||||
|
||||
#### New Features
|
||||
* Adds ability to restrict access through Class Level Permissions to only authenticated users [see docs](http://parseplatform.github.io/docs/ios/guide/#requires-authentication-permission-requires-parse-server---230)
|
||||
* Adds ability to strip sensitive data from `_User` responses, strips emails by default, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Adds password history support for password policies, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
|
||||
#### Improvements
|
||||
* Bump parse-server-s3-adapter to 1.0.6, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Using PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS let you create user sessions when passing {installationId: "xxx-xxx"} on signup in cloud code, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Add CLI option to pass `host` parameter when creating parse-server from CLI, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
|
||||
#### Bug fixes
|
||||
* Ensure batch routes are only using posix paths, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Ensure falsy options from CLI are properly taken into account, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Fixes issues affecting calls to `matchesKeyInQuery` with pointers.
|
||||
* Ensure that `select` keys can be changed in triggers (beforeFind...), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Housekeeping
|
||||
* Enables and enforces linting with eslint, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 2.2.25
|
||||
|
||||
Postgres support requires v9.5
|
||||
|
||||
#### New Features
|
||||
* Dockerizing Parse Server, thanks to [Kirill Kravinsky](https://github.com/woyorus)
|
||||
* Login with qq, wechat, weibo, thanks to [haifeizhang]()
|
||||
* Password policy, validation and expiration, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* Health check on /health, thanks to [Kirill Kravinsky](https://github.com/woyorus)
|
||||
* Reuse SchemaCache across requests option, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
|
||||
#### Improvements
|
||||
* Better support for CLI options, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Specity a database timeout with maxTimeMS, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Adds the username to reset password success pages, thanks to [Halim Qarroum](https://github.com/HQarroum)
|
||||
* Better support for Redis cache adapter, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Better coverage of Postgres, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
|
||||
#### Bug Fixes
|
||||
* Fixes issue when sending push to multiple installations, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fixes issues with twitter authentication, thanks to [jonas-db](https://github.com/jonas-db)
|
||||
* Ignore createdAt fields update, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
|
||||
* Improve support for array equality with LiveQuery, thanks to [David Poetzsch-Heffter](https://github.com/dpoetzsch)
|
||||
* Improve support for batch endpoint when serverURL and publicServerURL have different paths, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Support saving relation objects, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
|
||||
|
||||
### 2.2.24
|
||||
|
||||
#### New Features
|
||||
@@ -22,7 +430,7 @@ Postgres support requires v9.5
|
||||
* Better Postgres support, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Logs the function name when failing (#2963), thanks to [Michael Helvey](https://github.com/michaelhelvey)
|
||||
* CLI: forces closing the connections with SIGINT/SIGTERM (#2964), thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Reduce the number of calls to the _SCHEMA table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Reduce the number of calls to the `_SCHEMA` table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* LiveQuery: Support for Role ACL's, thanks to [Aaron Blondeau](https://github.com/aaron-blondeau-dose)
|
||||
|
||||
#### Bug Fixes
|
||||
@@ -69,7 +477,7 @@ Postgres support requires v9.5
|
||||
### 2.2.20
|
||||
|
||||
* New: Adds CloudCode handler for `beforeFind`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: RedisCacheAdapter for syncing schema, role and user caches across servers, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: RedisCacheAdapter for syncing schema, role and user caches across servers, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Latest master build available at `ParsePlatform/parse-server#latest`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Better support for upgradeToRevocableSession with missing session token, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Removes babel-polyfill runtime dependency, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at florent@flovilmart.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
+14
-1
@@ -9,7 +9,20 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request.
|
||||
* Run the tests for the file you are working on with `npm test spec/MyFile.spec.js`
|
||||
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
|
||||
* Lint your code by running `npm run lint` to make sure all your code is not gonna be rejected by the CI.
|
||||
* Never publish the lib folder.
|
||||
|
||||
##### Run your tests against Postgres (optional)
|
||||
|
||||
If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres.
|
||||
|
||||
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres npm test`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/.travis.yml#L37)
|
||||
* If your feature is intended to only work with MongoDB, you should disable PostgreSQL-specific tests with:
|
||||
|
||||
- `describe_only_db('mongo')` // will create a `describe` that runs only on mongoDB
|
||||
- `it_only_db('mongo')` // will make a test that only runs on mongo
|
||||
- `it_exclude_dbs(['postgres'])` // will make a test that runs against all DB's but postgres
|
||||
|
||||
##### Code of Conduct
|
||||
|
||||
This project adheres to the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/#Parse Server/fjm@fb.com). By participating, you are expected to honor this code.
|
||||
This project adheres to the [Contributor Covenant Code of Conduct](https://github.com/parse-community/parse-server/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to honor this code.
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
FROM node:boron
|
||||
|
||||
RUN mkdir -p /parse-server
|
||||
COPY ./ /parse-server/
|
||||
|
||||
RUN mkdir -p /parse-server/config
|
||||
VOLUME /parse-server/config
|
||||
|
||||
RUN mkdir -p /parse-server/cloud
|
||||
VOLUME /parse-server/cloud
|
||||
|
||||
WORKDIR /parse-server
|
||||
|
||||
RUN npm install && \
|
||||
npm run build
|
||||
|
||||
ENV PORT=1337
|
||||
|
||||
EXPOSE $PORT
|
||||
|
||||
ENTRYPOINT ["npm", "start", "--"]
|
||||
@@ -28,3 +28,7 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-----
|
||||
|
||||
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
|
||||
|
||||
@@ -31,3 +31,7 @@ necessarily infringed by the Software standing alone.
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
||||
|
||||
-----
|
||||
|
||||
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||

|
||||
|
||||
[](https://travis-ci.org/ParsePlatform/parse-server)
|
||||
[](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
|
||||
[](https://travis-ci.org/parse-community/parse-server)
|
||||
[](https://codecov.io/github/parse-community/parse-server?branch=master)
|
||||
[](https://www.npmjs.com/package/parse-server)
|
||||
|
||||
[](https://gitter.im/ParsePlatform/Chat)
|
||||
@@ -12,17 +12,30 @@ Parse Server works with the Express web application framework. It can be added t
|
||||
|
||||
# Getting Started
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
|
||||
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parse.com/learn/parse-server-video-series-april-2016/](http://blog.parse.com/learn/parse-server-video-series-april-2016/)
|
||||
|
||||
The fastest and easiest way to get started is to run MongoDB and Parse Server locally.
|
||||
|
||||
## Running Parse Server locally
|
||||
## Running Parse Server
|
||||
|
||||
### Locally
|
||||
```
|
||||
$ npm install -g parse-server mongodb-runner
|
||||
$ mongodb-runner start
|
||||
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test
|
||||
```
|
||||
***Note:*** *If installation with* `-g` *fails due to permission problems* (`npm ERR! code 'EACCES'`), *please refer to [this link](https://docs.npmjs.com/getting-started/fixing-npm-permissions).*
|
||||
|
||||
|
||||
### Inside a Docker container
|
||||
```
|
||||
$ docker build --tag parse-server .
|
||||
$ docker run --name my-mongo -d mongo
|
||||
$ docker run --name my-parse-server --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test
|
||||
```
|
||||
|
||||
You can use any arbitrary string as your application id and master key. These will be used by your clients to authenticate with the Parse Server.
|
||||
|
||||
@@ -32,7 +45,7 @@ That's it! You are now running a standalone version of Parse Server on your mach
|
||||
|
||||
### Saving your first object
|
||||
|
||||
Now that you're running Parse Server, it is time to save your first object. We'll use the [REST API](https://parse.com/docs/rest/guide), but you can easily do the same using any of the [Parse SDKs](https://parseplatform.github.io/#sdks). Run the following:
|
||||
Now that you're running Parse Server, it is time to save your first object. We'll use the [REST API](http://docs.parseplatform.org/rest/guide), but you can easily do the same using any of the [Parse SDKs](http://parseplatform.org/#sdks). Run the following:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
@@ -94,19 +107,19 @@ $ curl -X GET \
|
||||
|
||||
```
|
||||
|
||||
To learn more about using saving and querying objects on Parse Server, check out the [Parse documentation](https://parse.com/docs).
|
||||
To learn more about using saving and querying objects on Parse Server, check out the [Parse documentation](http://docs.parseplatform.org).
|
||||
|
||||
### Connect your app to Parse Server
|
||||
|
||||
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#using-parse-sdks-with-parse-server).
|
||||
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://github.com/parse-community/parse-server/wiki/Parse-Server-Guide#using-parse-sdks-with-parse-server).
|
||||
|
||||
## Running Parse Server elsewhere
|
||||
|
||||
Once you have a better understanding of how the project works, please refer to the [Parse Server wiki](https://github.com/ParsePlatform/parse-server/wiki) for in-depth guides to deploy Parse Server to major infrastructure providers. Read on to learn more about additional ways of running Parse Server.
|
||||
Once you have a better understanding of how the project works, please refer to the [Parse Server wiki](https://github.com/parse-community/parse-server/wiki) for in-depth guides to deploy Parse Server to major infrastructure providers. Read on to learn more about additional ways of running Parse Server.
|
||||
|
||||
### Parse Server Sample Application
|
||||
|
||||
We have provided a basic [Node.js application](https://github.com/ParsePlatform/parse-server-example) that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:
|
||||
We have provided a basic [Node.js application](https://github.com/parse-community/parse-server-example) that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:
|
||||
|
||||
* [Heroku and mLab](https://devcenter.heroku.com/articles/deploying-a-parse-server-to-heroku)
|
||||
* [AWS and Elastic Beanstalk](http://mobile.awsblog.com/post/TxCD57GZLM2JR/How-to-set-up-Parse-Server-on-AWS-using-AWS-Elastic-Beanstalk)
|
||||
@@ -114,10 +127,10 @@ We have provided a basic [Node.js application](https://github.com/ParsePlatform/
|
||||
* [Microsoft Azure](https://azure.microsoft.com/en-us/blog/azure-welcomes-parse-developers/)
|
||||
* [SashiDo](https://blog.sashido.io/tag/migration/)
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [NodeChef](https://nodechef.com/blog/post/6/migrate-from-parse-to-nodechef%E2%80%99s-managed-parse-server)
|
||||
* [Pivotal Web Services](https://github.com/cf-platform-eng/pws-parse-server)
|
||||
* [Back4app](http://blog.back4app.com/2016/03/01/quick-wizard-migration/)
|
||||
* [HyperDev](https://hyperdev.com/blog/use-parse-server-apps-backend-hyperdev/)
|
||||
* [Gomix](https://gomix.com/#!/project/parse-server)
|
||||
* [Flynn](https://flynn.io/blog/parse-apps-on-flynn)
|
||||
|
||||
### Parse Server + Express
|
||||
|
||||
@@ -149,7 +162,7 @@ For a full list of available options, run `parse-server --help`.
|
||||
|
||||
## Logging
|
||||
|
||||
Parse Server will, by default, will log:
|
||||
Parse Server will, by default, log:
|
||||
* to the console
|
||||
* daily rotating files as new line delimited JSON
|
||||
|
||||
@@ -159,15 +172,13 @@ Logs are also be viewable in Parse Dashboard.
|
||||
|
||||
**Want logs to be in placed in other folder?** Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
**Want to log specific levels?** Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL`
|
||||
|
||||
**Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc.)?** Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
# Documentation
|
||||
|
||||
The full documentation for Parse Server is available in the [wiki](https://github.com/ParsePlatform/parse-server/wiki). The [Parse Server guide](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide) is a good place to get started. If you're interested in developing for Parse Server, the [Development guide](https://github.com/ParsePlatform/parse-server/wiki/Development-Guide) will help you get set up.
|
||||
|
||||
## Migrating an Existing Parse App
|
||||
|
||||
The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. There are a few areas where Parse Server does not provide compatibility with the hosted version of Parse. Learn more in the [Migration guide](https://parse.com/migration).
|
||||
The full documentation for Parse Server is available in the [wiki](https://github.com/parse-community/parse-server/wiki). The [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/) is a good place to get started. If you're interested in developing for Parse Server, the [Development guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) will help you get set up.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -183,7 +194,7 @@ For the full list of available options, run `parse-server --help`.
|
||||
* `port` - The default port is 1337, specify this parameter to use a different port.
|
||||
* `serverURL` - URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code.
|
||||
* `cloud` - The absolute path to your cloud code `main.js` file.
|
||||
* `push` - Configuration options for APNS and GCM push. See the [Push Notifications wiki entry](https://github.com/ParsePlatform/parse-server/wiki/Push).
|
||||
* `push` - Configuration options for APNS and GCM push. See the [Push Notifications quick start](http://docs.parseplatform.org/parse-server/guide/#push-notifications_push-notifications-quick-start).
|
||||
|
||||
#### Client key options
|
||||
|
||||
@@ -199,15 +210,20 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `fileKey` - For migrated apps, this is necessary to provide access to files already hosted on Parse.
|
||||
* `allowClientClassCreation` - Set to false to disable client class creation. Defaults to true.
|
||||
* `enableAnonymousUsers` - Set to false to disable anonymous users. Defaults to true.
|
||||
* `oauth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/OAuth).
|
||||
* `auth` - Used to configure support for [3rd party authentication](http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).
|
||||
* `facebookAppIds` - An array of valid Facebook application IDs that users may authenticate with.
|
||||
* `mountPath` - Mount path for the server. Defaults to `/parse`.
|
||||
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
|
||||
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
|
||||
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
|
||||
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
|
||||
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
|
||||
* `logLevel` - Set the specific level you want to log. Defaults to `info`. The default logger uses the npm log levels as defined by the underlying winston logger. Check [Winston logging levels](https://github.com/winstonjs/winston#logging-levels) for details on values to specify.
|
||||
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
|
||||
* `revokeSessionOnPasswordReset` - When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
|
||||
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
|
||||
* `passwordPolicy` - Optional password policy rules to enforce.
|
||||
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
|
||||
* `middleware` - (CLI only), a module name, function that is an express middleware. When using the CLI, the express app will load it just **before** mounting parse-server on the mount path. This option is useful for injecting a monitoring middleware.
|
||||
* `masterKeyIps` - The array of ip addresses where masterKey usage will be restricted to only these ips. (Default to [] which means allow all ips). If you're using this feature and have `useMasterKey: true` in cloudcode, make sure that you put your own ip in this list.
|
||||
|
||||
##### Logging
|
||||
|
||||
@@ -269,6 +285,20 @@ var server = ParseServer({
|
||||
duration: 5, // duration policy setting determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000.
|
||||
threshold: 3, // threshold policy setting determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000.
|
||||
},
|
||||
// optional settings to enforce password policies
|
||||
passwordPolicy: {
|
||||
// Two optional settings to enforce strong passwords. Either one or both can be specified.
|
||||
// If both are specified, both checks must pass to accept the password
|
||||
// 1. a RegExp object or a regex string representing the pattern to enforce
|
||||
validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit
|
||||
// 2. a callback function to be invoked to validate the password
|
||||
validatorCallback: (password) => { return validatePassword(password) },
|
||||
doNotAllowUsername: true, // optional setting to disallow username in passwords
|
||||
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
|
||||
maxPasswordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history.
|
||||
//optional setting to set a validity duration for password reset links (in seconds)
|
||||
resetTokenValidityDuration: 24*60*60, // expire after 24 hours
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -313,19 +343,19 @@ Parse Server allows developers to choose from several options when hosting files
|
||||
* `S3Adapter`, which is backed by [Amazon S3](https://aws.amazon.com/s3/); or
|
||||
* `GCSAdapter`, which is backed by [Google Cloud Storage](https://cloud.google.com/storage/)
|
||||
|
||||
`GridStoreAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server wiki](https://github.com/ParsePlatform/parse-server/wiki/Configuring-File-Adapters).
|
||||
`GridStoreAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters).
|
||||
|
||||
# Support
|
||||
|
||||
For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities.
|
||||
|
||||
If you believe you've found an issue with Parse Server, make sure these boxes are checked before [reporting an issue](https://github.com/ParsePlatform/parse-server/issues):
|
||||
If you believe you've found an issue with Parse Server, make sure these boxes are checked before [reporting an issue](https://github.com/parse-community/parse-server/issues):
|
||||
|
||||
- [ ] You've met the [prerequisites](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites).
|
||||
- [ ] You've met the [prerequisites](http://docs.parseplatform.org/parse-server/guide/#prerequisites).
|
||||
|
||||
- [ ] You're running the [latest version](https://github.com/ParsePlatform/parse-server/releases) of Parse Server.
|
||||
- [ ] You're running the [latest version](https://github.com/parse-community/parse-server/releases) of Parse Server.
|
||||
|
||||
- [ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
|
||||
- [ ] You've searched through [existing issues](https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
|
||||
|
||||
# Want to ride the bleeding edge?
|
||||
|
||||
@@ -342,3 +372,7 @@ npm install parseplatform/parse-server.git#latest
|
||||
# Contributing
|
||||
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
|
||||
|
||||
-----
|
||||
|
||||
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
|
||||
|
||||
+2
-2
@@ -95,10 +95,10 @@ confirm 'Y' 'Do you want to continue? (Y/n): '
|
||||
check_node
|
||||
check_npm
|
||||
|
||||
printf "Setting up parse-server in %s" "${INSTALL_DIR}\n"
|
||||
printf "Setting up parse-server in %s\n" "${INSTALL_DIR}"
|
||||
|
||||
if [ -d "${INSTALL_DIR}" ]; then
|
||||
echo "{CHECK} ${INSTALL_DIR} exists"
|
||||
echo "${CHECK} ${INSTALL_DIR} exists"
|
||||
else
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
echo "${CHECK} Created ${INSTALL_DIR}"
|
||||
|
||||
+52
-44
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.2.25-beta.1",
|
||||
"version": "2.6.2",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -18,67 +18,75 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"bcryptjs": "2.3.0",
|
||||
"body-parser": "1.15.2",
|
||||
"commander": "2.9.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.18.1",
|
||||
"commander": "2.11.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.14.0",
|
||||
"express": "4.15.3",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.16.5",
|
||||
"lru-cache": "4.0.1",
|
||||
"mime": "1.3.4",
|
||||
"mongodb": "2.2.10",
|
||||
"multer": "1.2.0",
|
||||
"parse": "1.9.2",
|
||||
"lodash": "4.17.4",
|
||||
"lru-cache": "4.1.1",
|
||||
"mime": "1.4.0",
|
||||
"mongodb": "2.2.31",
|
||||
"multer": "1.3.0",
|
||||
"parse": "1.10.0",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"parse-server-push-adapter": "1.1.0",
|
||||
"parse-server-s3-adapter": "1.0.5",
|
||||
"parse-server-push-adapter": "2.0.0",
|
||||
"parse-server-s3-adapter": "1.2.0",
|
||||
"parse-server-simple-mailgun-adapter": "1.0.0",
|
||||
"pg-promise": "5.3.5",
|
||||
"redis": "2.6.3",
|
||||
"request": "2.76.0",
|
||||
"semver": "5.2.0",
|
||||
"tv4": "1.2.7",
|
||||
"winston": "2.3.0",
|
||||
"winston-daily-rotate-file": "1.3.1",
|
||||
"ws": "1.1.1"
|
||||
"pg-promise": "6.5.3",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.81.0",
|
||||
"semver": "5.4.0",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "^3.1.0",
|
||||
"winston": "2.3.1",
|
||||
"winston-daily-rotate-file": "1.5.0",
|
||||
"ws": "3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "6.18.0",
|
||||
"babel-core": "6.18.2",
|
||||
"babel-plugin-syntax-flow": "6.13.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.18.0",
|
||||
"babel-preset-es2015": "6.14.0",
|
||||
"babel-preset-stage-0": "6.5.0",
|
||||
"babel-register": "6.18.0",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.26.0",
|
||||
"babel-eslint": "^8.0.0",
|
||||
"babel-plugin-syntax-flow": "6.18.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.22.0",
|
||||
"babel-preset-env": "1.6.0",
|
||||
"babel-preset-es2015": "6.24.1",
|
||||
"babel-preset-stage-3": "6.24.1",
|
||||
"babel-register": "6.26.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "3.1.3",
|
||||
"deep-diff": "0.3.4",
|
||||
"gaze": "1.1.1",
|
||||
"istanbul": "1.0.0-alpha.1",
|
||||
"jasmine": "2.5.2",
|
||||
"jasmine-spec-reporter": "^2.7.0",
|
||||
"mongodb-runner": "3.4.0",
|
||||
"nodemon": "1.11.0",
|
||||
"request-promise": "4.1.1"
|
||||
"cross-env": "5.0.2",
|
||||
"deep-diff": "0.3.8",
|
||||
"eslint": "^4.4.1",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"gaze": "1.1.2",
|
||||
"jasmine": "2.8.0",
|
||||
"jasmine-spec-reporter": "^4.1.0",
|
||||
"mongodb-runner": "3.6.0",
|
||||
"nodemon": "1.12.1",
|
||||
"nyc": "^11.0.2",
|
||||
"request-promise": "4.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run build && node bin/dev",
|
||||
"build": "babel src/ -d lib/",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 node $COVERAGE_OPTION ./spec/support/runner.js",
|
||||
"test:win": "npm run pretest && cross-env NODE_ENV=test TESTING=1 node ./node_modules/jasmine/bin/jasmine.js && npm run posttest",
|
||||
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/istanbul cover' npm test",
|
||||
"coverage:win": "npm run pretest && cross-env NODE_ENV=test TESTING=1 node ./node_modules/babel-istanbul/lib/cli.js cover ./node_modules/jasmine/bin/jasmine.js && npm run posttest",
|
||||
"lint": "eslint --cache ./",
|
||||
"build": "babel src/ -d lib/ --copy-files",
|
||||
"pretest": "npm run lint",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 $COVERAGE_OPTION jasmine",
|
||||
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/nyc' npm test",
|
||||
"coverage:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 node ./node_modules/.bin/nyc ./node_modules/jasmine/bin/jasmine.js",
|
||||
"start": "node ./bin/parse-server",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.5"
|
||||
"node": ">=4.6"
|
||||
},
|
||||
"bin": {
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "0.8.7"
|
||||
"bcrypt": "1.0.3",
|
||||
"uws": "^8.14.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Invalid Link</h1>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This page is displayed when someone navigates to a verify email or reset password link
|
||||
but their security token is wrong. This can either mean the user has clicked on a
|
||||
stale link (i.e. re-click on a password reset link after resetting their password) or
|
||||
(rarely) this could be a sign of a malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Invalid Link</title>
|
||||
<style type='text/css'>
|
||||
.container {
|
||||
border-width: 0px;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-family: 'Helvetica Neue', Helvetica;
|
||||
font-size: 16px;
|
||||
height: 30px;
|
||||
line-height: 16px;
|
||||
margin: 45px 0px 0px 45px;
|
||||
padding: 0px 8px 0px 8px;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
color: #0067AB;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0 0 15px 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<script type="text/javascript">
|
||||
function getUrlParameter(name) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||
var results = regex.exec(location.search);
|
||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||
};
|
||||
|
||||
window.onload = addDataToForm;
|
||||
|
||||
function addDataToForm() {
|
||||
var username = getUrlParameter("username");
|
||||
document.getElementById("usernameField").value = username;
|
||||
|
||||
var appId = getUrlParameter("appId");
|
||||
document.getElementById("resendForm").action = '/apps/' + appId + '/resend_verification_email'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Invalid Verification Link</h1>
|
||||
<form id="resendForm" method="POST" action="/resend_verification_email">
|
||||
<input id="usernameField" class="form-control" name="username" type="hidden" value="">
|
||||
<button type="submit" class="btn btn-default">Resend Link</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This page is displayed when someone navigates to a verify email link with an invalid
|
||||
security token and requests a link resend. This page is displayed when the username from
|
||||
the original link is invalid or if the email of that user has already been verfieid when
|
||||
the resend request is made
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Invalid Link</title>
|
||||
<style type='text/css'>
|
||||
.container {
|
||||
border-width: 0px;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-family: 'Helvetica Neue', Helvetica;
|
||||
font-size: 16px;
|
||||
height: 30px;
|
||||
line-height: 16px;
|
||||
margin: 45px 0px 0px 45px;
|
||||
padding: 0px 8px 0px 8px;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
color: #0067AB;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0 0 15px 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>No link sent. User not found or email already verified</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This page is displayed when someone navigates to a verify email link with an invalid
|
||||
security token and requests a link resend. This page is displayed when the username
|
||||
from the original verification link has been found and a new verification link has
|
||||
been successfully sent to the corresponding stored email
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Invalid Link</title>
|
||||
<style type='text/css'>
|
||||
.container {
|
||||
border-width: 0px;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-family: 'Helvetica Neue', Helvetica;
|
||||
font-size: 16px;
|
||||
height: 30px;
|
||||
line-height: 16px;
|
||||
margin: 45px 0px 0px 45px;
|
||||
padding: 0px 8px 0px 8px;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
color: #0067AB;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0 0 15px 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Link Sent! Check your email.</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -30,4 +30,4 @@ git config user.name "Travis CI"
|
||||
git config user.email "github@fb.com"
|
||||
git add .
|
||||
git commit -m "Deploy master to LATEST branch"
|
||||
git push --force --quiet "https://${GH_TOKEN}@github.com/parseplatform/parse-server.git" master:latest
|
||||
git push --force --quiet "https://${GH_TOKEN}@github.com/parse-community/parse-server.git" master:latest
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
"""
|
||||
https://github.com/dmakhno/travis_after_all/blob/master/travis_after_all.py
|
||||
https://github.com/graphql/graphql-js/blob/master/resources/travis_after_all.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
try:
|
||||
import urllib.request as urllib2
|
||||
except ImportError:
|
||||
import urllib2
|
||||
|
||||
log = logging.getLogger("travis.leader")
|
||||
log.addHandler(logging.StreamHandler())
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
TRAVIS_JOB_NUMBER = 'TRAVIS_JOB_NUMBER'
|
||||
TRAVIS_BUILD_ID = 'TRAVIS_BUILD_ID'
|
||||
POLLING_INTERVAL = 'LEADER_POLLING_INTERVAL'
|
||||
|
||||
build_id = os.getenv(TRAVIS_BUILD_ID)
|
||||
polling_interval = int(os.getenv(POLLING_INTERVAL, '5'))
|
||||
|
||||
#assume, first job is the leader
|
||||
is_leader = lambda job_number: job_number.endswith('.1')
|
||||
|
||||
if not os.getenv(TRAVIS_JOB_NUMBER):
|
||||
# seems even for builds with only one job, this won't get here
|
||||
log.fatal("Don't use defining leader for build without matrix")
|
||||
exit(1)
|
||||
elif is_leader(os.getenv(TRAVIS_JOB_NUMBER)):
|
||||
log.info("This is a leader")
|
||||
else:
|
||||
#since python is subprocess, env variables are exported back via file
|
||||
with open(".to_export_back", "w") as export_var:
|
||||
export_var.write("BUILD_MINION=YES")
|
||||
log.info("This is a minion")
|
||||
exit(0)
|
||||
|
||||
|
||||
class MatrixElement(object):
|
||||
def __init__(self, json_raw):
|
||||
self.is_finished = json_raw['finished_at'] is not None
|
||||
self.is_succeeded = json_raw['result'] == 0
|
||||
self.number = json_raw['number']
|
||||
self.is_leader = is_leader(self.number)
|
||||
|
||||
|
||||
def matrix_snapshot():
|
||||
"""
|
||||
:return: Matrix List
|
||||
"""
|
||||
response = urllib2.build_opener().open("https://api.travis-ci.org/builds/{0}".format(build_id)).read()
|
||||
raw_json = json.loads(response)
|
||||
matrix_without_leader = [MatrixElement(element) for element in raw_json["matrix"]]
|
||||
return matrix_without_leader
|
||||
|
||||
|
||||
def wait_others_to_finish():
|
||||
def others_finished():
|
||||
"""
|
||||
Dumps others to finish
|
||||
Leader cannot finish, it is working now
|
||||
:return: tuple(True or False, List of not finished jobs)
|
||||
"""
|
||||
snapshot = matrix_snapshot()
|
||||
finished = [el.is_finished for el in snapshot if not el.is_leader]
|
||||
return reduce(lambda a, b: a and b, finished), [el.number for el in snapshot if
|
||||
not el.is_leader and not el.is_finished]
|
||||
|
||||
while True:
|
||||
finished, waiting_list = others_finished()
|
||||
if finished: break
|
||||
log.info("Leader waits for minions {0}...".format(waiting_list)) # just in case do not get "silence timeout"
|
||||
time.sleep(polling_interval)
|
||||
|
||||
|
||||
try:
|
||||
wait_others_to_finish()
|
||||
|
||||
final_snapshot = matrix_snapshot()
|
||||
log.info("Final Results: {0}".format([(e.number, e.is_succeeded) for e in final_snapshot]))
|
||||
|
||||
BUILD_AGGREGATE_STATUS = 'BUILD_AGGREGATE_STATUS'
|
||||
others_snapshot = [el for el in final_snapshot if not el.is_leader]
|
||||
if reduce(lambda a, b: a and b, [e.is_succeeded for e in others_snapshot]):
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "others_succeeded"
|
||||
elif reduce(lambda a, b: a and b, [not e.is_succeeded for e in others_snapshot]):
|
||||
log.error("Others Failed")
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "others_failed"
|
||||
else:
|
||||
log.warn("Others Unknown")
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "unknown"
|
||||
#since python is subprocess, env variables are exported back via file
|
||||
with open(".to_export_back", "w") as export_var:
|
||||
export_var.write("BUILD_LEADER=YES {0}={1}".format(BUILD_AGGREGATE_STATUS, os.environ[BUILD_AGGREGATE_STATUS]))
|
||||
|
||||
except Exception as e:
|
||||
log.fatal(e)
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"env": {
|
||||
"jasmine": true
|
||||
},
|
||||
"globals": {
|
||||
"Parse": true,
|
||||
"reconfigureServer": true,
|
||||
"createTestUser": true,
|
||||
"jfail": true,
|
||||
"ok": true,
|
||||
"strictEqual": true,
|
||||
"TestObject": true,
|
||||
"Item": true,
|
||||
"Container": true,
|
||||
"equal": true,
|
||||
"notEqual": true,
|
||||
"it_exclude_dbs": true,
|
||||
"describe_only_db": true,
|
||||
"describe_only": true,
|
||||
"on_db": true,
|
||||
"defaultConfiguration": true,
|
||||
"expectSuccess": true,
|
||||
"range": true,
|
||||
"expectError": true,
|
||||
"jequal": true,
|
||||
"create": true,
|
||||
"arrayContains": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": [0]
|
||||
}
|
||||
}
|
||||
+183
-183
@@ -5,14 +5,14 @@ const Config = require("../src/Config");
|
||||
var loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(user => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Invalid username/password.') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
.then(() => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Invalid username/password.') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -20,14 +20,14 @@ var isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(user => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
.then(() => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}, waitTime);
|
||||
});
|
||||
};
|
||||
@@ -39,26 +39,26 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'unlimited',
|
||||
publicServerURL: 'http://localhost:1337/1',
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername('username1');
|
||||
user.setPassword('password');
|
||||
return user.signUp(null);
|
||||
})
|
||||
.then(user => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(err => {
|
||||
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername('username1');
|
||||
user.setPassword('password');
|
||||
return user.signUp(null);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(err => {
|
||||
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if duration is set to an invalid number', done => {
|
||||
@@ -70,19 +70,19 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
var config = new Config('test');
|
||||
fail('set duration to an invalid number test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('set duration to an invalid number test failed');
|
||||
done();
|
||||
} else {
|
||||
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
done();
|
||||
} else {
|
||||
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if threshold is set to an invalid number', done => {
|
||||
@@ -94,19 +94,19 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
var config = new Config('test');
|
||||
fail('set threshold to an invalid number test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('set threshold to an invalid number test failed');
|
||||
done();
|
||||
} else {
|
||||
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
done();
|
||||
} else {
|
||||
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if threshold is < 1', done => {
|
||||
@@ -118,19 +118,19 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
var config = new Config('test');
|
||||
fail('threshold value < 1 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('threshold value < 1 is invalid test failed');
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if threshold is > 999', done => {
|
||||
@@ -142,19 +142,19 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
var config = new Config('test');
|
||||
fail('threshold value > 999 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('threshold value > 999 is invalid test failed');
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if duration is <= 0', done => {
|
||||
@@ -166,19 +166,19 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
var config = new Config('test');
|
||||
fail('duration value < 1 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('duration value < 1 is invalid test failed');
|
||||
done();
|
||||
} else {
|
||||
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if duration is > 99999', done => {
|
||||
@@ -190,19 +190,19 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
var config = new Config('test');
|
||||
fail('duration value > 99999 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('duration value > 99999 is invalid test failed');
|
||||
done();
|
||||
} else {
|
||||
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('lock account if failed login attempts are above threshold', done => {
|
||||
@@ -214,28 +214,28 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username2");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username2', 'wrong password', 1, 1);
|
||||
})
|
||||
.then(() => {
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username2");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username2', 'wrong password', 1, 1);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('lock account for accountPolicy.duration minutes if failed login attempts are above threshold', done => {
|
||||
@@ -247,32 +247,32 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username3");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
|
||||
})
|
||||
.then(() => {
|
||||
// account should still be locked even after 2 seconds.
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 2000);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('account should be locked for duration mins test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username3");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
|
||||
})
|
||||
.then(() => {
|
||||
// account should still be locked even after 2 seconds.
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 2000);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('account should be locked for duration mins test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allow login for locked account after accountPolicy.duration minutes', done => {
|
||||
@@ -284,35 +284,35 @@ describe("Account Lockout Policy: ", () => {
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username4");
|
||||
user.setPassword("correct password");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
// allow locked user to login after 3 seconds with a valid userid and password
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn('username4', 'correct password')
|
||||
.then(user => resolve())
|
||||
.catch(err => reject(err));
|
||||
}, 3001);
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username4");
|
||||
user.setPassword("correct password");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
// allow locked user to login after 3 seconds with a valid userid and password
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn('username4', 'correct password')
|
||||
.then(() => resolve())
|
||||
.catch(err => reject(err));
|
||||
}, 3001);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("AdaptableController", ()=>{
|
||||
});
|
||||
|
||||
it("should fail setting the wrong adapter to the controller", (done) => {
|
||||
function WrongAdapter() {};
|
||||
function WrongAdapter() {}
|
||||
var adapter = new FilesAdapter();
|
||||
var controller = new FilesController(adapter);
|
||||
var otherAdapter = new WrongAdapter();
|
||||
@@ -42,7 +42,7 @@ describe("AdaptableController", ()=>{
|
||||
});
|
||||
|
||||
it("should fail to instantiate a controller with wrong adapter", (done) => {
|
||||
function WrongAdapter() {};
|
||||
function WrongAdapter() {}
|
||||
var adapter = new WrongAdapter();
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
@@ -59,10 +59,10 @@ describe("AdaptableController", ()=>{
|
||||
|
||||
it("should accept an object adapter", (done) => {
|
||||
var adapter = {
|
||||
createFile: function(config, filename, data) { },
|
||||
deleteFile: function(config, filename) { },
|
||||
getFileData: function(config, filename) { },
|
||||
getFileLocation: function(config, filename) { },
|
||||
createFile: function() { },
|
||||
deleteFile: function() { },
|
||||
getFileData: function() { },
|
||||
getFileLocation: function() { },
|
||||
}
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
@@ -71,11 +71,11 @@ describe("AdaptableController", ()=>{
|
||||
});
|
||||
|
||||
it("should accept an object adapter", (done) => {
|
||||
function AGoodAdapter() {};
|
||||
AGoodAdapter.prototype.createFile = function(config, filename, data) { };
|
||||
AGoodAdapter.prototype.deleteFile = function(config, filename) { };
|
||||
AGoodAdapter.prototype.getFileData = function(config, filename) { };
|
||||
AGoodAdapter.prototype.getFileLocation = function(config, filename) { };
|
||||
function AGoodAdapter() {}
|
||||
AGoodAdapter.prototype.createFile = function() { };
|
||||
AGoodAdapter.prototype.deleteFile = function() { };
|
||||
AGoodAdapter.prototype.getFileData = function() { };
|
||||
AGoodAdapter.prototype.getFileLocation = function() { };
|
||||
|
||||
var adapter = new AGoodAdapter();
|
||||
expect(() => {
|
||||
|
||||
@@ -3,6 +3,7 @@ var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
||||
var FilesAdapter = require("parse-server-fs-adapter").default;
|
||||
var S3Adapter = require("parse-server-s3-adapter").default;
|
||||
var ParsePushAdapter = require("parse-server-push-adapter").default;
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("AdapterLoader", ()=>{
|
||||
|
||||
@@ -106,8 +107,9 @@ describe("AdapterLoader", ()=>{
|
||||
|
||||
it("should load push adapter from options", (done) => {
|
||||
var options = {
|
||||
ios: {
|
||||
bundleId: 'bundle.id'
|
||||
android: {
|
||||
senderId: 'yolo',
|
||||
apiKey: 'yolo'
|
||||
}
|
||||
}
|
||||
expect(() => {
|
||||
@@ -118,6 +120,30 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load custom push adapter from string (#3544)", (done) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockPushAdapter");
|
||||
var options = {
|
||||
ios: {
|
||||
bundleId: 'bundle.id'
|
||||
}
|
||||
}
|
||||
const pushAdapterOptions = {
|
||||
adapter: adapterPath,
|
||||
options
|
||||
};
|
||||
expect(() => {
|
||||
reconfigureServer({
|
||||
push: pushAdapterOptions,
|
||||
}).then(() => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const pushAdapter = config.pushWorker.adapter;
|
||||
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
|
||||
expect(pushAdapter.options).toEqual(pushAdapterOptions);
|
||||
done();
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should load S3Adapter from direct passing", (done) => {
|
||||
var s3Adapter = new S3Adapter("key", "secret", "bucket")
|
||||
expect(() => {
|
||||
|
||||
+12
-12
@@ -1,15 +1,15 @@
|
||||
const analyticsAdapter = {
|
||||
appOpened: function(parameters, req) {},
|
||||
trackEvent: function(eventName, parameters, req) {}
|
||||
appOpened: function() {},
|
||||
trackEvent: function() {}
|
||||
}
|
||||
|
||||
describe('AnalyticsController', () => {
|
||||
it('should track a simple event', (done) => {
|
||||
|
||||
|
||||
spyOn(analyticsAdapter, 'trackEvent').and.callThrough();
|
||||
reconfigureServer({
|
||||
analyticsAdapter
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.Analytics.track('MyEvent', {
|
||||
key: 'value',
|
||||
count: '0'
|
||||
@@ -17,7 +17,7 @@ describe('AnalyticsController', () => {
|
||||
}).then(() => {
|
||||
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
|
||||
var lastCall = analyticsAdapter.trackEvent.calls.first();
|
||||
let args = lastCall.args;
|
||||
const args = lastCall.args;
|
||||
expect(args[0]).toEqual('MyEvent');
|
||||
expect(args[1]).toEqual({
|
||||
dimensions: {
|
||||
@@ -26,18 +26,18 @@ describe('AnalyticsController', () => {
|
||||
}
|
||||
});
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should track a app opened event', (done) => {
|
||||
|
||||
|
||||
spyOn(analyticsAdapter, 'appOpened').and.callThrough();
|
||||
reconfigureServer({
|
||||
analyticsAdapter
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.Analytics.track('AppOpened', {
|
||||
key: 'value',
|
||||
count: '0'
|
||||
@@ -45,7 +45,7 @@ describe('AnalyticsController', () => {
|
||||
}).then(() => {
|
||||
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
|
||||
var lastCall = analyticsAdapter.appOpened.calls.first();
|
||||
let args = lastCall.args;
|
||||
const args = lastCall.args;
|
||||
expect(args[0]).toEqual({
|
||||
dimensions: {
|
||||
key: 'value',
|
||||
@@ -53,9 +53,9 @@ describe('AnalyticsController', () => {
|
||||
}
|
||||
});
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
var auth = require('../src/Auth');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
var AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
|
||||
|
||||
describe('AudiencesRouter', () => {
|
||||
it('uses find condition from request.body', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {
|
||||
where: {
|
||||
query: '{ "test": "android" }'
|
||||
}
|
||||
},
|
||||
query: {},
|
||||
info: {}
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
var results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses find condition from request.query', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
where: {
|
||||
'query': '{ "test": "android" }'
|
||||
}
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
var results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with limit = 0', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
new Config('test');
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with count = 1', done => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
count: 1
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('query installations with limit = 0 and count = 1', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create, read, update and delete audiences throw api', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
expect(results.results[0].name).toEqual('My Audience');
|
||||
expect(results.results[0].query.deviceType).toEqual('ios');
|
||||
Parse._request('GET', `push_audiences/${results.results[0].objectId}`, {}, { useMasterKey: true }).then((results) => {
|
||||
expect(results.name).toEqual('My Audience');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request('PUT', `push_audiences/${results.objectId}`, { name: 'My Audience 2' }, { useMasterKey: true }).then(() => {
|
||||
Parse._request('GET', `push_audiences/${results.objectId}`, {}, { useMasterKey: true }).then((results) => {
|
||||
expect(results.name).toEqual('My Audience 2');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request('DELETE', `push_audiences/${results.objectId}`, {}, { useMasterKey: true }).then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
|
||||
expect(results.results.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only create with master key', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only find with master key', (done) => {
|
||||
Parse._request('GET', 'push_audiences', {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only get with master key', (done) => {
|
||||
Parse._request('GET', `push_audiences/someId`, {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only update with master key', (done) => {
|
||||
Parse._request('PUT', `push_audiences/someId`, { name: 'My Audience 2' })
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only delete with master key', (done) => {
|
||||
Parse._request('DELETE', `push_audiences/someId`, {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('should support legacy parse.com audience fields', (done) => {
|
||||
const database = (new Config(Parse.applicationId)).database.adapter.database;
|
||||
const now = new Date();
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then((audience) => {
|
||||
database.collection('test__Audience').updateOne(
|
||||
{ _id: audience.objectId },
|
||||
{
|
||||
$set: {
|
||||
times_used: 1,
|
||||
_last_used: now
|
||||
}
|
||||
},
|
||||
{},
|
||||
(error) => {
|
||||
expect(error).toEqual(null)
|
||||
database.collection('test__Audience').find({ _id: audience.objectId}).toArray(
|
||||
(error, rows) => {
|
||||
expect(error).toEqual(null)
|
||||
expect(rows[0]['times_used']).toEqual(1);
|
||||
expect(rows[0]['_last_used']).toEqual(now);
|
||||
Parse._request('GET', 'push_audiences/' + audience.objectId, {}, {useMasterKey: true})
|
||||
.then((audience) => {
|
||||
expect(audience.name).toEqual('My Audience');
|
||||
expect(audience.query.deviceType).toEqual('ios');
|
||||
expect(audience.timesUsed).toEqual(1);
|
||||
expect(audience.lastUsed).toEqual(now.toISOString());
|
||||
done();
|
||||
})
|
||||
.catch((error) => { done.fail(error); })
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to search on audiences', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
const query = {"timesUsed": {"$exists": false}, "lastUsed": {"$exists": false}};
|
||||
Parse._request('GET', 'push_audiences?order=-createdAt&limit=1', {where: query}, {useMasterKey: true})
|
||||
.then((results) => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
const audience = results.results[0];
|
||||
expect(audience.name).toEqual("neverUsed");
|
||||
done();
|
||||
})
|
||||
.catch((error) => { done.fail(error); })
|
||||
})
|
||||
});
|
||||
});
|
||||
+3
-4
@@ -4,7 +4,6 @@ describe('Auth', () => {
|
||||
describe('getUserRoles', () => {
|
||||
var auth;
|
||||
var config;
|
||||
var cacheController;
|
||||
var currentRoles = null;
|
||||
var currentUserId = 'userId';
|
||||
|
||||
@@ -51,8 +50,8 @@ describe('Auth', () => {
|
||||
expect(roles).toEqual(currentRoles);
|
||||
return auth.getUserRoles()
|
||||
})
|
||||
.then((roles) => auth.getUserRoles())
|
||||
.then((roles) => auth.getUserRoles())
|
||||
.then(() => auth.getUserRoles())
|
||||
.then(() => auth.getUserRoles())
|
||||
.then((roles) => {
|
||||
// Should only call the cache adapter once.
|
||||
expect(config.cacheController.role.get.calls.count()).toEqual(1);
|
||||
@@ -79,7 +78,7 @@ describe('Auth', () => {
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should properly handle bcrypt upgrade', (done) => {
|
||||
it('should properly handle bcrypt upgrade', (done) => {
|
||||
var bcryptOriginal = require('bcrypt-nodejs');
|
||||
var bcryptNew = require('bcryptjs');
|
||||
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
var request = require('request');
|
||||
var Config = require("../src/Config");
|
||||
var defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
||||
var authenticationLoader = require('../src/Adapters/Auth');
|
||||
var path = require('path');
|
||||
|
||||
describe('AuthenticationProviders', function() {
|
||||
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
|
||||
it("Should validate structure of " + providerName, (done) => {
|
||||
var provider = require("../src/Adapters/Auth/" + providerName);
|
||||
jequal(typeof provider.validateAuthData, "function");
|
||||
jequal(typeof provider.validateAppId, "function");
|
||||
const authDataPromise = provider.validateAuthData({}, {});
|
||||
const validateAppIdPromise = provider.validateAppId("app", "key", {});
|
||||
jequal(authDataPromise.constructor, Promise.prototype.constructor);
|
||||
jequal(validateAppIdPromise.constructor, Promise.prototype.constructor);
|
||||
authDataPromise.then(()=>{}, ()=>{});
|
||||
validateAppIdPromise.then(()=>{}, ()=>{});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var getMockMyOauthProvider = function() {
|
||||
return {
|
||||
authData: {
|
||||
id: "12345",
|
||||
access_token: "12345",
|
||||
expiration_date: new Date().toJSON(),
|
||||
},
|
||||
shouldError: false,
|
||||
loggedOut: false,
|
||||
synchronizedUserId: null,
|
||||
synchronizedAuthToken: null,
|
||||
synchronizedExpiration: null,
|
||||
|
||||
authenticate: function(options) {
|
||||
if (this.shouldError) {
|
||||
options.error(this, "An error occurred");
|
||||
} else if (this.shouldCancel) {
|
||||
options.error(this, null);
|
||||
} else {
|
||||
options.success(this, this.authData);
|
||||
}
|
||||
},
|
||||
restoreAuthentication: function(authData) {
|
||||
if (!authData) {
|
||||
this.synchronizedUserId = null;
|
||||
this.synchronizedAuthToken = null;
|
||||
this.synchronizedExpiration = null;
|
||||
return true;
|
||||
}
|
||||
this.synchronizedUserId = authData.id;
|
||||
this.synchronizedAuthToken = authData.access_token;
|
||||
this.synchronizedExpiration = authData.expiration_date;
|
||||
return true;
|
||||
},
|
||||
getAuthType: function() {
|
||||
return "myoauth";
|
||||
},
|
||||
deauthenticate: function() {
|
||||
this.loggedOut = true;
|
||||
this.restoreAuthentication(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Parse.User.extend({
|
||||
extended: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var createOAuthUser = function(callback) {
|
||||
return createOAuthUserWithSessionToken(undefined, callback);
|
||||
}
|
||||
|
||||
var createOAuthUserWithSessionToken = function(token, callback) {
|
||||
var jsonBody = {
|
||||
authData: {
|
||||
myoauth: getMockMyOauthProvider().authData
|
||||
}
|
||||
};
|
||||
|
||||
var options = {
|
||||
headers: {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': 'yolo',
|
||||
'X-Parse-Session-Token': token,
|
||||
'Content-Type': 'application/json' },
|
||||
url: 'http://localhost:8378/1/users',
|
||||
body: jsonBody,
|
||||
json: true
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
request.post(options, (err, res, body) => {
|
||||
resolve({err, res, body});
|
||||
if (callback) {
|
||||
callback(err, res, body);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("should create user with REST API", done => {
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = body;
|
||||
ok(b.sessionToken);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
var sessionToken = b.sessionToken;
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo('sessionToken', sessionToken);
|
||||
q.first({useMasterKey: true}).then((res) => {
|
||||
if (!res) {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
return;
|
||||
}
|
||||
expect(res.get("installationId")).toEqual('yolo');
|
||||
done();
|
||||
}).fail(() => {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("should only create a single user with REST API", (done) => {
|
||||
var objectId;
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = body
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
objectId = b.objectId;
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = body;
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
expect(b.objectId).toBe(objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to link if session token don't match user", (done) => {
|
||||
Parse.User.signUp('myUser', 'password').then((user) => {
|
||||
return createOAuthUserWithSessionToken(user.getSessionToken());
|
||||
}).then(() => {
|
||||
return Parse.User.logOut();
|
||||
}).then(() => {
|
||||
return Parse.User.signUp('myUser2', 'password');
|
||||
}).then((user) => {
|
||||
return createOAuthUserWithSessionToken(user.getSessionToken());
|
||||
}).then(({ body }) => {
|
||||
expect(body.code).toBe(208);
|
||||
expect(body.error).toBe('this auth is already used');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it("unlink and link with custom provider", (done) => {
|
||||
var provider = getMockMyOauthProvider();
|
||||
Parse.User._registerAuthenticationProvider(provider);
|
||||
Parse.User._logInWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||
|
||||
model._unlinkFrom("myoauth", {
|
||||
success: function(model) {
|
||||
|
||||
ok(!model._isLinked("myoauth"),
|
||||
"User should not be linked to myoauth");
|
||||
ok(!provider.synchronizedUserId, "User id should be cleared");
|
||||
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
|
||||
ok(!provider.synchronizedExpiration,
|
||||
"Expiration should be cleared");
|
||||
// make sure the auth data is properly deleted
|
||||
var config = new Config(Parse.applicationId);
|
||||
config.database.adapter.find('_User', {
|
||||
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
|
||||
}, { objectId: model.id }, {})
|
||||
.then(res => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0]._auth_data_myoauth).toBeUndefined();
|
||||
expect(res[0]._auth_data_myoauth).not.toBeNull();
|
||||
|
||||
model._linkWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(provider.synchronizedUserId, "User id should have a value");
|
||||
ok(provider.synchronizedAuthToken,
|
||||
"Auth token should have a value");
|
||||
ok(provider.synchronizedExpiration,
|
||||
"Expiration should have a value");
|
||||
ok(model._isLinked("myoauth"),
|
||||
"User should be linked to myoauth");
|
||||
done();
|
||||
},
|
||||
error: function() {
|
||||
ok(false, "linking again should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function() {
|
||||
ok(false, "unlinking should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function() {
|
||||
ok(false, "linking should have worked");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function validateValidator(validator) {
|
||||
expect(typeof validator).toBe('function');
|
||||
}
|
||||
|
||||
function validateAuthenticationHandler(authenticationHandler) {
|
||||
expect(authenticationHandler).not.toBeUndefined();
|
||||
expect(typeof authenticationHandler.getValidatorForProvider).toBe('function');
|
||||
expect(typeof authenticationHandler.getValidatorForProvider).toBe('function');
|
||||
}
|
||||
|
||||
function validateAuthenticationAdapter(authAdapter) {
|
||||
expect(authAdapter).not.toBeUndefined();
|
||||
if (!authAdapter) { return; }
|
||||
expect(typeof authAdapter.validateAuthData).toBe('function');
|
||||
expect(typeof authAdapter.validateAppId).toBe('function');
|
||||
}
|
||||
|
||||
it('properly loads custom adapter', (done) => {
|
||||
var validAuthData = {
|
||||
id: 'hello',
|
||||
token: 'world'
|
||||
}
|
||||
const adapter = {
|
||||
validateAppId: function() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
validateAuthData: function(authData) {
|
||||
if (authData.id == validAuthData.id && authData.token == validAuthData.token) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
|
||||
const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough();
|
||||
const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough();
|
||||
|
||||
const authenticationHandler = authenticationLoader({
|
||||
customAuthentication: adapter
|
||||
});
|
||||
|
||||
validateAuthenticationHandler(authenticationHandler);
|
||||
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
|
||||
validateValidator(validator);
|
||||
|
||||
validator(validAuthData).then(() => {
|
||||
expect(authDataSpy).toHaveBeenCalled();
|
||||
// AppIds are not provided in the adapter, should not be called
|
||||
expect(appIdSpy).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads custom adapter module object', (done) => {
|
||||
const authenticationHandler = authenticationLoader({
|
||||
customAuthentication: path.resolve('./spec/support/CustomAuth.js')
|
||||
});
|
||||
|
||||
validateAuthenticationHandler(authenticationHandler);
|
||||
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
|
||||
validateValidator(validator);
|
||||
|
||||
validator({
|
||||
token: 'my-token'
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads custom adapter module object', (done) => {
|
||||
const authenticationHandler = authenticationLoader({
|
||||
customAuthentication: { module: path.resolve('./spec/support/CustomAuthFunction.js'), options: { token: 'valid-token' }}
|
||||
});
|
||||
|
||||
validateAuthenticationHandler(authenticationHandler);
|
||||
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
|
||||
validateValidator(validator);
|
||||
|
||||
validator({
|
||||
token: 'valid-token'
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads a default adapter with options', () => {
|
||||
const options = {
|
||||
facebook: {
|
||||
appIds: ['a', 'b']
|
||||
}
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('facebook', options);
|
||||
validateAuthenticationAdapter(adapter);
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions).toEqual(options.facebook);
|
||||
});
|
||||
|
||||
it('properly loads a custom adapter with options', () => {
|
||||
const options = {
|
||||
custom: {
|
||||
validateAppId: () => {},
|
||||
validateAuthData: () => {},
|
||||
appIds: ['a', 'b']
|
||||
}
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('custom', options);
|
||||
validateAuthenticationAdapter(adapter);
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions).toEqual(options.custom);
|
||||
});
|
||||
});
|
||||
+122
-75
@@ -1,29 +1,31 @@
|
||||
'use strict';
|
||||
var commander = require("../src/cli/utils/commander").default;
|
||||
import commander from '../src/cli/utils/commander';
|
||||
import definitions from '../src/cli/definitions/parse-server';
|
||||
import liveQueryDefinitions from '../src/cli/definitions/parse-live-query-server';
|
||||
|
||||
var definitions = {
|
||||
"arg0": "PROGRAM_ARG_0",
|
||||
"arg1": {
|
||||
env: "PROGRAM_ARG_1",
|
||||
var testDefinitions = {
|
||||
'arg0': 'PROGRAM_ARG_0',
|
||||
'arg1': {
|
||||
env: 'PROGRAM_ARG_1',
|
||||
required: true
|
||||
},
|
||||
"arg2": {
|
||||
env: "PROGRAM_ARG_2",
|
||||
'arg2': {
|
||||
env: 'PROGRAM_ARG_2',
|
||||
action: function(value) {
|
||||
var value = parseInt(value);
|
||||
if (!Number.isInteger(value)) {
|
||||
throw "arg2 is invalid";
|
||||
var intValue = parseInt(value);
|
||||
if (!Number.isInteger(intValue)) {
|
||||
throw 'arg2 is invalid';
|
||||
}
|
||||
return value;
|
||||
return intValue;
|
||||
}
|
||||
},
|
||||
"arg3": {},
|
||||
"arg4": {
|
||||
default: "arg4Value"
|
||||
'arg3': {},
|
||||
'arg4': {
|
||||
default: 'arg4Value'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe("commander additions", () => {
|
||||
describe('commander additions', () => {
|
||||
afterEach((done) => {
|
||||
commander.options = [];
|
||||
delete commander.arg0;
|
||||
@@ -32,107 +34,152 @@ describe("commander additions", () => {
|
||||
delete commander.arg3;
|
||||
delete commander.arg4;
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should load properly definitions from args", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg1", "arg1Value", "--arg2", "2", "--arg3", "some"]);
|
||||
expect(commander.arg0).toEqual("arg0Value");
|
||||
expect(commander.arg1).toEqual("arg1Value");
|
||||
it('should load properly definitions from args', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg1', 'arg1Value', '--arg2', '2', '--arg3', 'some']);
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1Value');
|
||||
expect(commander.arg2).toEqual(2);
|
||||
expect(commander.arg3).toEqual("some");
|
||||
expect(commander.arg4).toEqual("arg4Value");
|
||||
expect(commander.arg3).toEqual('some');
|
||||
expect(commander.arg4).toEqual('arg4Value');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load properly definitions from env", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
it('should load properly definitions from env', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse([], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "3",
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': '3',
|
||||
});
|
||||
expect(commander.arg0).toEqual("arg0ENVValue");
|
||||
expect(commander.arg1).toEqual("arg1ENVValue");
|
||||
expect(commander.arg0).toEqual('arg0ENVValue');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
expect(commander.arg2).toEqual(3);
|
||||
expect(commander.arg4).toEqual("arg4Value");
|
||||
expect(commander.arg4).toEqual('arg4Value');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load properly use args over env", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg4", "anotherArg4"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "4",
|
||||
it('should load properly use args over env', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg4', ''], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': '4',
|
||||
'PROGRAM_ARG_4': 'arg4ENVValue'
|
||||
});
|
||||
expect(commander.arg0).toEqual("arg0Value");
|
||||
expect(commander.arg1).toEqual("arg1ENVValue");
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
expect(commander.arg2).toEqual(4);
|
||||
expect(commander.arg4).toEqual("anotherArg4");
|
||||
expect(commander.arg4).toEqual('');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail in action as port is invalid", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
it('should fail in action as port is invalid', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(()=> {
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "hello",
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': 'hello',
|
||||
});
|
||||
}).toThrow("arg2 is invalid");
|
||||
}).toThrow('arg2 is invalid');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should not override config.json", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "./spec/configs/CLIConfig.json"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
it('should not override config.json', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', './spec/configs/CLIConfig.json'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
});
|
||||
let options = commander.getOptions();
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe("hello"); //config value
|
||||
expect(options.arg3).toBe('hello'); //config value
|
||||
expect(options.arg4).toBe('/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail with invalid values in JSON", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
it('should fail with invalid values in JSON', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "./spec/configs/CLIConfigFail.json"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', './spec/configs/CLIConfigFail.json'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
});
|
||||
}).toThrow("arg2 is invalid")
|
||||
}).toThrow('arg2 is invalid');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail when too many apps are set", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
it('should fail when too many apps are set', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(["node","./CLI.spec.js","./spec/configs/CLIConfigFailTooManyApps.json"]);
|
||||
}).toThrow("Multiple apps are not supported")
|
||||
commander.parse(['node','./CLI.spec.js','./spec/configs/CLIConfigFailTooManyApps.json']);
|
||||
}).toThrow('Multiple apps are not supported');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load config from apps", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node", "./CLI.spec.js", "./spec/configs/CLIConfigApps.json"]);
|
||||
let options = commander.getOptions();
|
||||
expect(options.arg1).toBe("my_app");
|
||||
it('should load config from apps', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigApps.json']);
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg1).toBe('my_app');
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe("hello"); //config value
|
||||
expect(options.arg3).toBe('hello'); //config value
|
||||
expect(options.arg4).toBe('/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail when passing an invalid arguement", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
it('should fail when passing an invalid arguement', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(["node", "./CLI.spec.js", "./spec/configs/CLIConfigUnknownArg.json"]);
|
||||
}).toThrow('error: unknown option myArg')
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigUnknownArg.json']);
|
||||
}).toThrow('error: unknown option myArg');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('definitions', () => {
|
||||
it('should have valid types', () => {
|
||||
for (const key in definitions) {
|
||||
const definition = definitions[key];
|
||||
expect(typeof definition).toBe('object');
|
||||
if (typeof definition.env !== 'undefined') {
|
||||
expect(typeof definition.env).toBe('string');
|
||||
}
|
||||
expect(typeof definition.help).toBe('string');
|
||||
if (typeof definition.required !== 'undefined') {
|
||||
expect(typeof definition.required).toBe('boolean');
|
||||
}
|
||||
if (typeof definition.action !== 'undefined') {
|
||||
expect(typeof definition.action).toBe('function');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw when using deprecated facebookAppIds', () => {
|
||||
expect(() => {
|
||||
definitions.facebookAppIds.action()
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('LiveQuery definitions', () => {
|
||||
it('should have valid types', () => {
|
||||
for (const key in liveQueryDefinitions) {
|
||||
const definition = liveQueryDefinitions[key];
|
||||
expect(typeof definition).toBe('object');
|
||||
if (typeof definition.env !== 'undefined') {
|
||||
expect(typeof definition.env).toBe('string');
|
||||
}
|
||||
expect(typeof definition.help).toBe('string');
|
||||
if (typeof definition.required !== 'undefined') {
|
||||
expect(typeof definition.required).toBe('boolean');
|
||||
}
|
||||
if (typeof definition.action !== 'undefined') {
|
||||
expect(typeof definition.action).toBe('function');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,13 +62,13 @@ describe('CacheController', function() {
|
||||
|
||||
it('should handle cache rejections', (done) => {
|
||||
|
||||
FakeCacheAdapter.get = () => Promise.reject();
|
||||
FakeCacheAdapter.get = () => Promise.reject();
|
||||
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
cache.get('foo').then(done, () => {
|
||||
fail('Promise should not be rejected.');
|
||||
});
|
||||
cache.get('foo').then(done, () => {
|
||||
fail('Promise should not be rejected.');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+23
-23
@@ -1,31 +1,31 @@
|
||||
var ClientSDK = require('../src/ClientSDK');
|
||||
|
||||
describe('ClientSDK', () => {
|
||||
it('should properly parse the SDK versions', () => {
|
||||
let clientSDKFromVersion = ClientSDK.fromString;
|
||||
expect(clientSDKFromVersion('i1.1.1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1.1.1'
|
||||
});
|
||||
expect(clientSDKFromVersion('i1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1'
|
||||
});
|
||||
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
|
||||
sdk: 'apple-tv',
|
||||
version: '1.13.0'
|
||||
});
|
||||
expect(clientSDKFromVersion('js1.9.0')).toEqual({
|
||||
sdk: 'js',
|
||||
version: '1.9.0'
|
||||
});
|
||||
describe('ClientSDK', () => {
|
||||
it('should properly parse the SDK versions', () => {
|
||||
const clientSDKFromVersion = ClientSDK.fromString;
|
||||
expect(clientSDKFromVersion('i1.1.1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1.1.1'
|
||||
});
|
||||
expect(clientSDKFromVersion('i1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1'
|
||||
});
|
||||
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
|
||||
sdk: 'apple-tv',
|
||||
version: '1.13.0'
|
||||
});
|
||||
expect(clientSDKFromVersion('js1.9.0')).toEqual({
|
||||
sdk: 'js',
|
||||
version: '1.9.0'
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly sastisfy', () => {
|
||||
|
||||
it('should properly sastisfy', () => {
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})("js1.9.0")).toBe(true);
|
||||
|
||||
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})("js2.0.0")).toBe(true);
|
||||
@@ -38,4 +38,4 @@ describe('ClientSDK', () => {
|
||||
js: '>=1.9.0'
|
||||
})(undefined)).toBe(true);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+712
-376
File diff suppressed because it is too large
Load Diff
+211
-194
@@ -1,229 +1,246 @@
|
||||
'use strict';
|
||||
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const fs = require('fs');
|
||||
|
||||
const loremFile = __dirname + '/support/lorem.txt';
|
||||
|
||||
describe("Cloud Code Logger", () => {
|
||||
let user;
|
||||
let user;
|
||||
|
||||
beforeEach(done => {
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
return reconfigureServer({
|
||||
// useful to flip to false for fine tuning :).
|
||||
silent: true,
|
||||
}).then(() => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => done())
|
||||
});
|
||||
beforeEach(done => {
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
return reconfigureServer({
|
||||
// useful to flip to false for fine tuning :).
|
||||
silent: true,
|
||||
}).then(() => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => done())
|
||||
});
|
||||
});
|
||||
|
||||
// Note that helpers takes care of logout.
|
||||
// see helpers.js:afterEach
|
||||
|
||||
it("should expose log to functions", done => {
|
||||
var logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define("loggerTest", (req, res) => {
|
||||
req.log.info('logTest', 'info log', { info: 'some log' });
|
||||
req.log.error('logTest', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
});
|
||||
|
||||
// Note that helpers takes care of logout.
|
||||
// see helpers.js:afterEach
|
||||
Parse.Cloud.run('loggerTest').then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 });
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
const lastLogs = res.slice(0, 3);
|
||||
const cloudFunctionMessage = lastLogs[0];
|
||||
const errorMessage = lastLogs[1];
|
||||
const infoMessage = lastLogs[2];
|
||||
expect(cloudFunctionMessage.level).toBe('info');
|
||||
expect(cloudFunctionMessage.params).toEqual({});
|
||||
expect(cloudFunctionMessage.message).toMatch(/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/);
|
||||
expect(cloudFunctionMessage.functionName).toEqual('loggerTest');
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('logTest error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('logTest info log');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should expose log to functions", done => {
|
||||
var logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
it('trigger should obfuscate password', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define("loggerTest", (req, res) => {
|
||||
req.log.info('logTest', 'info log', { info: 'some log' });
|
||||
req.log.error('logTest', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
});
|
||||
|
||||
Parse.Cloud.run('loggerTest').then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 });
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
let lastLogs = res.slice(0, 3);
|
||||
let cloudFunctionMessage = lastLogs[0];
|
||||
let errorMessage = lastLogs[1];
|
||||
let infoMessage = lastLogs[2];
|
||||
expect(cloudFunctionMessage.level).toBe('info');
|
||||
expect(cloudFunctionMessage.params).toEqual({});
|
||||
expect(cloudFunctionMessage.message).toMatch(/Ran cloud function loggerTest for user [^ ]* with:\n Input: {}\n Result: {}/);
|
||||
expect(cloudFunctionMessage.functionName).toEqual('loggerTest');
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('logTest error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('logTest info log');
|
||||
done();
|
||||
});
|
||||
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
|
||||
res.success(req.object);
|
||||
});
|
||||
|
||||
it('trigger should obfuscate password', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.User.signUp('tester123', 'abc')
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((res) => {
|
||||
const entry = res[0];
|
||||
expect(entry.message).not.toMatch(/password":"abc/);
|
||||
expect(entry.message).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
|
||||
res.success(req.object);
|
||||
});
|
||||
it("should expose log to trigger", (done) => {
|
||||
var logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.User.signUp('tester123', 'abc')
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((res) => {
|
||||
const entry = res[0];
|
||||
expect(entry.message).not.toMatch(/password":"abc/);
|
||||
expect(entry.message).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
|
||||
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
});
|
||||
|
||||
it("should expose log to trigger", (done) => {
|
||||
var logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const obj = new Parse.Object('MyObject');
|
||||
obj.save().then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 })
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
const lastLogs = res.slice(0, 3);
|
||||
const cloudTriggerMessage = lastLogs[0];
|
||||
const errorMessage = lastLogs[1];
|
||||
const infoMessage = lastLogs[2];
|
||||
expect(cloudTriggerMessage.level).toBe('info');
|
||||
expect(cloudTriggerMessage.triggerType).toEqual('beforeSave');
|
||||
expect(cloudTriggerMessage.message).toMatch(/beforeSave triggered for MyObject for user [^ ]*\n {2}Input: {}\n {2}Result: {}/);
|
||||
expect(cloudTriggerMessage.user).toBe(user.id);
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('beforeSave MyObject error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('beforeSave MyObject info log');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
|
||||
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
});
|
||||
it('should truncate really long lines when asked to', () => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const longString = fs.readFileSync(loremFile, 'utf8');
|
||||
const truncatedString = logController.truncateLogMessage(longString);
|
||||
expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)'
|
||||
});
|
||||
|
||||
let obj = new Parse.Object('MyObject');
|
||||
obj.save().then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 })
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
let lastLogs = res.slice(0, 3);
|
||||
let cloudTriggerMessage = lastLogs[0];
|
||||
let errorMessage = lastLogs[1];
|
||||
let infoMessage = lastLogs[2];
|
||||
expect(cloudTriggerMessage.level).toBe('info');
|
||||
expect(cloudTriggerMessage.triggerType).toEqual('beforeSave');
|
||||
expect(cloudTriggerMessage.message).toMatch(/beforeSave triggered for MyObject for user [^ ]*\n Input: {}\n Result: {}/);
|
||||
expect(cloudTriggerMessage.user).toBe(user.id);
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('beforeSave MyObject error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('beforeSave MyObject info log');
|
||||
done();
|
||||
});
|
||||
it('should truncate input and result of long lines', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const longString = fs.readFileSync(loremFile, 'utf8');
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success(req.params);
|
||||
});
|
||||
|
||||
it('should truncate really long lines when asked to', () => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const longString = fs.readFileSync(loremFile, 'utf8');
|
||||
const truncatedString = logController.truncateLogMessage(longString);
|
||||
expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)'
|
||||
Parse.Cloud.run('aFunction', { longString })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should log an afterSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.afterSave("MyObject", () => { });
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
const log = logs[0];
|
||||
expect(log.triggerType).toEqual('afterSave');
|
||||
done();
|
||||
})
|
||||
// catch errors - not that the error is actually useful :(
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should log a denied beforeSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
res.error('uh oh!');
|
||||
});
|
||||
|
||||
it('should truncate input and result of long lines', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const longString = fs.readFileSync(loremFile, 'utf8');
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success(req.params);
|
||||
});
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(
|
||||
() => done.fail('this is not supposed to succeed'),
|
||||
() => new Promise(resolve => setTimeout(resolve, 100))
|
||||
)
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[1]; // 0 is the 'uh oh!' from rejection...
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.error).toEqual({ code: 141, message: 'uh oh!' });
|
||||
done()
|
||||
});
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { longString })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n Input: {.*?\(truncated\)$/m);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
it('should log cloud function success', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success('it worked!');
|
||||
});
|
||||
|
||||
it('should log an afterSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.afterSave("MyObject", (req) => { });
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
const log = logs[0];
|
||||
expect(log.triggerType).toEqual('afterSave');
|
||||
done();
|
||||
})
|
||||
// catch errors - not that the error is actually useful :(
|
||||
.then(null, e => done.fail(e));
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Result: "it worked!/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should log cloud function failure', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.error('it failed!');
|
||||
});
|
||||
|
||||
it('should log a denied beforeSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
res.error('uh oh!');
|
||||
});
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(null, () => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[2];
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.message).toMatch(
|
||||
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error: {"code":141,"message":"it failed!"}/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(
|
||||
() => done.fail('this is not supposed to succeed'),
|
||||
e => logController.getLogs({ from: Date.now() - 500, size: 1000 })
|
||||
)
|
||||
.then(logs => {
|
||||
const log = logs[1]; // 0 is the 'uh oh!' from rejection...
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.error).toEqual({ code: 141, message: 'uh oh!' });
|
||||
done()
|
||||
});
|
||||
xit('should log a changed beforeSave indicating a change', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
const myObj = req.object;
|
||||
myObj.set('aChange', true);
|
||||
res.success(myObj);
|
||||
});
|
||||
|
||||
it('should log cloud function success', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success('it worked!');
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n Input: {"foo":"bar"}\n Result: "it worked!/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should log cloud function failure', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.error('it failed!');
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(null, () => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[1];
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.message).toMatch(
|
||||
/Failed running cloud function aFunction for user [^ ]* with:\n Input: {"foo":"bar"}\n Error: {"code":141,"message":"it failed!"}/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit('should log a changed beforeSave indicating a change', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
const myObj = req.object;
|
||||
myObj.set('aChange', true);
|
||||
res.success(myObj);
|
||||
});
|
||||
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
// expect the log to indicate that it has changed
|
||||
/*
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(() => {
|
||||
// expect the log to indicate that it has changed
|
||||
/*
|
||||
Here's what it looks like on parse.com...
|
||||
|
||||
Input: {"original":{"clientVersion":"1","createdAt":"2016-06-02T05:29:08.694Z","image":{"__type":"File","name":"tfss-xxxxxxxx.png","url":"http://files.parsetfss.com/xxxxxxxx.png"},"lastScanDate":{"__type":"Date","iso":"2016-06-02T05:28:58.135Z"},"localIdentifier":"XXXXX","objectId":"OFHMX7ZUcI","status":... (truncated)
|
||||
Result: Update changed to {"object":{"__type":"Pointer","className":"Emoticode","objectId":"ksrq7z3Ehc"},"imageThumb":{"__type":"File","name":"tfss-xxxxxxx.png","url":"http://files.parsetfss.com/xxxxx.png"},"status":"success"}
|
||||
*/
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(JSON.stringify(e)));
|
||||
}).pend('needs more work.....');
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(JSON.stringify(e)));
|
||||
}).pend('needs more work.....');
|
||||
|
||||
it('cloud function should obfuscate password', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('testFunction', (req, res) => {
|
||||
res.success(1002,'verify code success');
|
||||
});
|
||||
|
||||
Parse.Cloud.run('testFunction', {username:'hawk',password:'123456'})
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((res) => {
|
||||
const entry = res[0];
|
||||
expect(entry.params.password).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
var DatabaseController = require('../src/Controllers/DatabaseController.js');
|
||||
var validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
describe('DatabaseController', function() {
|
||||
|
||||
describe('validateQuery', function() {
|
||||
|
||||
it('should restructure simple cases of SERVER-13732', (done) => {
|
||||
var query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
|
||||
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not restructure SERVER-13732 queries with $nears', (done) => {
|
||||
var query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}});
|
||||
|
||||
query = {$or: [{a: 1}, {b: 1}], c: {$near: {}}};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$near: {}}});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('should push refactored keys down a tree for SERVER-13732', (done) => {
|
||||
var query = {a: 1, $or: [{$or: [{b: 1}, {b: 2}]},
|
||||
{$or: [{c: 1}, {c: 2}]}]};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{$or: [{b: 1, a: 1}, {b: 2, a: 1}]},
|
||||
{$or: [{c: 1, a: 1}, {c: 2, a: 1}]}]});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reject invalid queries', (done) => {
|
||||
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should accept valid queries', (done) => {
|
||||
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
+544
-292
@@ -1,12 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
const MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
|
||||
const request = require('request');
|
||||
const requestp = require('request-promise');
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("Email Verification Token Expiration: ", () => {
|
||||
|
||||
it('show the invalid link page, if the user clicks on the verify email link after the email verify token expires', done => {
|
||||
it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
@@ -23,28 +23,28 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||
done();
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
|
||||
done();
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', done => {
|
||||
@@ -64,35 +64,35 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if user clicks on the email verify link before email verification token expiration then show the verify email success page', done => {
|
||||
@@ -112,23 +112,23 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
|
||||
done();
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', done => {
|
||||
@@ -148,30 +148,30 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if user clicks on the email verify link before email verification token expiration then user should be able to login', done => {
|
||||
@@ -191,31 +191,31 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.get('emailVerified')).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.get('emailVerified')).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', done => {
|
||||
@@ -235,29 +235,30 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('sets_email_verify_token_expires_at');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
let user = results[0];
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(false);
|
||||
expect(typeof user._email_verify_token).toBe('string');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('object');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
user.setUsername('sets_email_verify_token_expires_at');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
const user = results[0];
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(false);
|
||||
expect(typeof user._email_verify_token).toBe('string');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('object');
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => {
|
||||
@@ -277,98 +278,42 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("unsets_email_verify_token_expires_at");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
.then(() => {
|
||||
user.setUsername("unsets_email_verify_token_expires_at");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
let config = new Config('test');
|
||||
return config.database.find('_User', {username: 'unsets_email_verify_token_expires_at'}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
return results[0];
|
||||
})
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(true);
|
||||
expect(typeof user._email_verify_token).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
var serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
};
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get(sendEmailOptions.link, { followRedirect: false, })
|
||||
.on('error', error => reject(error))
|
||||
.on('response', (response) => {
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
resolve(user.fetch());
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'unsets_email_verify_token_expires_at'}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
return results[0];
|
||||
})
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(true);
|
||||
expect(typeof user._email_verify_token).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
|
||||
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
|
||||
it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
@@ -387,53 +332,107 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
// just get the user again - DO NOT email verify the user
|
||||
return user.fetch();
|
||||
})
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
|
||||
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get(sendEmailOptions.link, { followRedirect: false, })
|
||||
.on('error', error => reject(error))
|
||||
.on('response', (response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
resolve(user.fetch());
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
|
||||
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
var serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
};
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
// just get the user again - DO NOT email verify the user
|
||||
return user.fetch();
|
||||
})
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
|
||||
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', done => {
|
||||
|
||||
let db;
|
||||
|
||||
let user = new Parse.User();
|
||||
const user = new Parse.User();
|
||||
let userBeforeEmailReset;
|
||||
|
||||
let sendEmailOptions;
|
||||
let emailAdapter = {
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
};
|
||||
let serverConfig = {
|
||||
const serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
@@ -442,45 +441,297 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
};
|
||||
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("newEmailVerifyTokenOnEmailReset");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userFromDb => {
|
||||
expect(typeof userFromDb).toBe('object');
|
||||
userBeforeEmailReset = userFromDb;
|
||||
.then(() => {
|
||||
user.setUsername("newEmailVerifyTokenOnEmailReset");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userFromDb => {
|
||||
expect(typeof userFromDb).toBe('object');
|
||||
userBeforeEmailReset = userFromDb;
|
||||
|
||||
// trigger another token generation by setting the email
|
||||
user.set('email', 'user@parse.com');
|
||||
return new Promise((resolve, reject) => {
|
||||
// trigger another token generation by setting the email
|
||||
user.set('email', 'user@parse.com');
|
||||
return new Promise((resolve) => {
|
||||
// wait for half a sec to get a new expiration time
|
||||
setTimeout( () => resolve(user.save()), 500 );
|
||||
setTimeout(() => resolve(user.save()), 500);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userAfterEmailReset => {
|
||||
expect(typeof userAfterEmailReset).toBe('object');
|
||||
expect(userBeforeEmailReset._email_verify_token).not.toEqual(userAfterEmailReset._email_verify_token);
|
||||
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(userAfterEmailReset.__email_verify_token_expires_at);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should send a new verification email when a resend is requested and the user is UNVERIFIED', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
.then(() => {
|
||||
user.setUsername('resends_verification_token');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false // this promise is only rejected if the call itself failed
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(sendVerificationEmailCallCount).toBe(2);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send a new verification email when a resend is requested and the user is VERIFIED', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(userAfterEmailReset => {
|
||||
expect(typeof userAfterEmailReset).toBe('object');
|
||||
expect(userBeforeEmailReset._email_verify_token).not.toEqual(userAfterEmailReset._email_verify_token);
|
||||
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(userAfterEmailReset.__email_verify_token_expires_at);
|
||||
done();
|
||||
.then(() => {
|
||||
user.setUsername('no_new_verification_token_once_verified');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return requestp.get({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirect: false,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false // this promise is only rejected if the call itself failed
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send a new verification email if this user does not exist', done => {
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
})
|
||||
.then(response => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if no email is supplied', done => {
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
request.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
}, (err, response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.code).toBe(Parse.Error.EMAIL_MISSING);
|
||||
expect(response.body.error).toBe('you must provide an email');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if email is not a string', done => {
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
request.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {email: 3},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
}, (err, response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
|
||||
expect(response.body.error).toBe('you must provide a valid email string');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('client should not see the _email_verify_token_expires_at field', done => {
|
||||
@@ -500,29 +751,30 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
|
||||
done();
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.catch(error => {
|
||||
.then(() => {
|
||||
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
|
||||
}).catch((err) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('Enable single schema cache', () => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.find(config, nobody, 'Bar', {type: 1});
|
||||
}).then((response) => {
|
||||
}).then(() => {
|
||||
fail('Should throw error');
|
||||
done();
|
||||
}, (error) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('EventEmitterPubSub', function() {
|
||||
subscriber.unsubscribe('testChannel');
|
||||
// Register mock checked for subscriber
|
||||
var isCalled = false;
|
||||
subscriber.on('message', function(channel, message) {
|
||||
subscriber.on('message', function() {
|
||||
isCalled = true;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
var Config = require("../src/Config");
|
||||
var FilesController = require('../src/Controllers/FilesController').default;
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
const Config = require("../src/Config");
|
||||
const FilesController = require('../src/Controllers/FilesController').default;
|
||||
|
||||
const mockAdapter = {
|
||||
createFile: () => {
|
||||
return Parse.Promise.reject(new Error('it failed'));
|
||||
},
|
||||
deleteFile: () => { },
|
||||
getFileData: () => { },
|
||||
getFileLocation: () => 'xyz'
|
||||
}
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe("FilesController",() =>{
|
||||
@@ -26,5 +36,30 @@ describe("FilesController",() =>{
|
||||
expect(anObject.aFile.url).toEqual("http://an.url");
|
||||
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should create a server log on failure', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
reconfigureServer({ filesAdapter: mockAdapter })
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
|
||||
.then(() => new Parse.File("yolo.txt", [1,2,3], "text/plain").save())
|
||||
.then(
|
||||
() => done.fail('should not succeed'),
|
||||
() => setImmediate(() => Parse.Promise.as('done'))
|
||||
)
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
// we get two logs here: 1. the source of the failure to save the file
|
||||
// and 2 the message that will be sent back to the client.
|
||||
const log1 = logs.pop();
|
||||
expect(log1.level).toBe('error');
|
||||
expect(log1.message).toBe('it failed');
|
||||
const log2 = logs.pop();
|
||||
expect(log2.level).toBe('error');
|
||||
expect(log2.code).toBe(130);
|
||||
expect(log2.message).toBe('Could not store file.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+45
-45
@@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var httpRequest = require("../src/cloud-code/httpRequest"),
|
||||
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
|
||||
bodyParser = require('body-parser'),
|
||||
express = require("express");
|
||||
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
|
||||
bodyParser = require('body-parser'),
|
||||
express = require("express");
|
||||
|
||||
var port = 13371;
|
||||
var httpRequestServer = "http://localhost:"+port;
|
||||
var httpRequestServer = "http://localhost:" + port;
|
||||
|
||||
var app = express();
|
||||
app.use(bodyParser.json({ 'type': '*/*' }));
|
||||
@@ -39,7 +39,7 @@ app.listen(13371);
|
||||
describe("httpRequest", () => {
|
||||
it("should do /hello", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/hello"
|
||||
url: httpRequestServer + "/hello"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
|
||||
@@ -55,7 +55,7 @@ describe("httpRequest", () => {
|
||||
it("should do /hello with callback and promises", (done) => {
|
||||
var calls = 0;
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/hello",
|
||||
url: httpRequestServer + "/hello",
|
||||
success: function() { calls++; },
|
||||
error: function() { calls++; }
|
||||
}).then(function(httpResponse){
|
||||
@@ -74,7 +74,7 @@ describe("httpRequest", () => {
|
||||
it("should do not follow redirects by default", (done) => {
|
||||
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/301"
|
||||
url: httpRequestServer + "/301"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(301);
|
||||
done();
|
||||
@@ -87,7 +87,7 @@ describe("httpRequest", () => {
|
||||
it("should follow redirects when set", (done) => {
|
||||
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/301",
|
||||
url: httpRequestServer + "/301",
|
||||
followRedirects: true
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
@@ -104,7 +104,7 @@ describe("httpRequest", () => {
|
||||
it("should fail on 404", (done) => {
|
||||
var calls = 0;
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/404",
|
||||
url: httpRequestServer + "/404",
|
||||
success: function() {
|
||||
calls++;
|
||||
fail("should not succeed");
|
||||
@@ -124,8 +124,8 @@ describe("httpRequest", () => {
|
||||
|
||||
it("should fail on 404", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/404",
|
||||
}).then(function(httpResponse){
|
||||
url: httpRequestServer + "/404",
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, function(httpResponse){
|
||||
@@ -141,9 +141,9 @@ describe("httpRequest", () => {
|
||||
var calls = 0;
|
||||
httpRequest({
|
||||
method: "POST",
|
||||
url: httpRequestServer+"/echo",
|
||||
url: httpRequestServer + "/echo",
|
||||
body: {
|
||||
foo: "bar"
|
||||
foo: "bar"
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -155,17 +155,17 @@ describe("httpRequest", () => {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({foo: "bar"});
|
||||
done();
|
||||
}, function(httpResponse){
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should encode a query string body by default", (done) => {
|
||||
let options = {
|
||||
const options = {
|
||||
body: {"foo": "bar"},
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual('foo=bar');
|
||||
expect(result.headers['Content-Type']).toEqual('application/x-www-form-urlencoded');
|
||||
done();
|
||||
@@ -173,30 +173,30 @@ describe("httpRequest", () => {
|
||||
})
|
||||
|
||||
it("should encode a JSON body", (done) => {
|
||||
let options = {
|
||||
const options = {
|
||||
body: {"foo": "bar"},
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual('{"foo":"bar"}');
|
||||
done();
|
||||
|
||||
})
|
||||
it("should encode a www-form body", (done) => {
|
||||
let options = {
|
||||
it("should encode a www-form body", (done) => {
|
||||
const options = {
|
||||
body: {"foo": "bar", "bar": "baz"},
|
||||
headers: {'cOntent-tYpe': 'application/x-www-form-urlencoded'}
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual("foo=bar&bar=baz");
|
||||
done();
|
||||
});
|
||||
it("should not encode a wrong content type", (done) => {
|
||||
let options = {
|
||||
const options = {
|
||||
body:{"foo": "bar", "bar": "baz"},
|
||||
headers: {'cOntent-tYpe': 'mime/jpeg'}
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual({"foo": "bar", "bar": "baz"});
|
||||
done();
|
||||
});
|
||||
@@ -218,9 +218,9 @@ describe("httpRequest", () => {
|
||||
|
||||
it("should params object to query string", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/qs",
|
||||
url: httpRequestServer + "/qs",
|
||||
params: {
|
||||
foo: "bar"
|
||||
foo: "bar"
|
||||
}
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
@@ -234,7 +234,7 @@ describe("httpRequest", () => {
|
||||
|
||||
it("should params string to query string", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/qs",
|
||||
url: httpRequestServer + "/qs",
|
||||
params: "foo=bar&foo2=bar2"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
@@ -247,7 +247,7 @@ describe("httpRequest", () => {
|
||||
});
|
||||
|
||||
it('should not crash with undefined body', () => {
|
||||
let httpResponse = new HTTPResponse({});
|
||||
const httpResponse = new HTTPResponse({});
|
||||
expect(httpResponse.body).toBeUndefined();
|
||||
expect(httpResponse.data).toBeUndefined();
|
||||
expect(httpResponse.text).toBeUndefined();
|
||||
@@ -255,24 +255,24 @@ describe("httpRequest", () => {
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body string', () => {
|
||||
let httpResponse = new HTTPResponse({}, 'hello');
|
||||
const httpResponse = new HTTPResponse({}, 'hello');
|
||||
expect(httpResponse.text).toBe('hello');
|
||||
expect(httpResponse.data).toBe(undefined);
|
||||
expect(httpResponse.body).toBe('hello');
|
||||
|
||||
let serialized = JSON.stringify(httpResponse);
|
||||
let result = JSON.parse(serialized);
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toBe('hello');
|
||||
expect(result.data).toBe(undefined);
|
||||
expect(result.body).toBe(undefined);
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body object', () => {
|
||||
let httpResponse = new HTTPResponse({}, {foo: "bar"});
|
||||
let encodedResponse = Parse._encode(httpResponse);
|
||||
let serialized = JSON.stringify(httpResponse);
|
||||
let result = JSON.parse(serialized);
|
||||
|
||||
const httpResponse = new HTTPResponse({}, {foo: "bar"});
|
||||
Parse._encode(httpResponse);
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
|
||||
expect(httpResponse.text).toEqual('{"foo":"bar"}');
|
||||
expect(httpResponse.data).toEqual({foo: 'bar'});
|
||||
expect(httpResponse.body).toEqual({foo: 'bar'});
|
||||
@@ -283,29 +283,29 @@ describe("httpRequest", () => {
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body buffer string', () => {
|
||||
let httpResponse = new HTTPResponse({}, new Buffer('hello'));
|
||||
const httpResponse = new HTTPResponse({}, new Buffer('hello'));
|
||||
expect(httpResponse.text).toBe('hello');
|
||||
expect(httpResponse.data).toBe(undefined);
|
||||
|
||||
let serialized = JSON.stringify(httpResponse);
|
||||
let result = JSON.parse(serialized);
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toBe('hello');
|
||||
expect(result.data).toBe(undefined);
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body buffer JSON Object', () => {
|
||||
let json = '{"foo":"bar"}';
|
||||
let httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
let serialized = JSON.stringify(httpResponse);
|
||||
let result = JSON.parse(serialized);
|
||||
const json = '{"foo":"bar"}';
|
||||
const httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toEqual('{"foo":"bar"}');
|
||||
expect(result.data).toEqual({foo: 'bar'});
|
||||
});
|
||||
|
||||
it('serialized httpResponse with Parse._encode should be allright', () => {
|
||||
let json = '{"foo":"bar"}';
|
||||
let httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
let encoded = Parse._encode(httpResponse);
|
||||
const json = '{"foo":"bar"}';
|
||||
const httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
const encoded = Parse._encode(httpResponse);
|
||||
let foundData, foundText, foundBody = false;
|
||||
for(var key in encoded) {
|
||||
if (key == 'data') {
|
||||
|
||||
@@ -3,7 +3,7 @@ const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
|
||||
|
||||
describe('InMemoryCache', function() {
|
||||
var BASE_TTL = {
|
||||
ttl: 10
|
||||
ttl: 100
|
||||
};
|
||||
var NO_EXPIRE_TTL = {
|
||||
ttl: NaN
|
||||
@@ -15,7 +15,7 @@ describe('InMemoryCache', function() {
|
||||
|
||||
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
}
|
||||
@@ -28,7 +28,7 @@ describe('InMemoryCache', function() {
|
||||
var value = cache.get(KEY);
|
||||
expect(value).toEqual(VALUE);
|
||||
|
||||
wait(BASE_TTL.ttl * 5).then(() => {
|
||||
wait(BASE_TTL.ttl * 2).then(() => {
|
||||
value = cache.get(KEY)
|
||||
expect(value).toEqual(null);
|
||||
done();
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('InMemoryCacheAdapter', function() {
|
||||
var VALUE = 'world';
|
||||
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
}
|
||||
@@ -15,8 +15,6 @@ describe('InMemoryCacheAdapter', function() {
|
||||
ttl: NaN
|
||||
});
|
||||
|
||||
var noop = () => {};
|
||||
|
||||
// Verify all methods return promises.
|
||||
Promise.all([
|
||||
cache.put(KEY, VALUE),
|
||||
|
||||
@@ -28,18 +28,18 @@ describe('InstallationsRouter', () => {
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses find condition from request.query', (done) => {
|
||||
@@ -66,18 +66,18 @@ describe('InstallationsRouter', () => {
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with limit = 0', (done) => {
|
||||
@@ -100,21 +100,21 @@ describe('InstallationsRouter', () => {
|
||||
info: {}
|
||||
};
|
||||
|
||||
var config = new Config('test');
|
||||
new Config('test');
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with count = 1', done => {
|
||||
@@ -139,18 +139,18 @@ describe('InstallationsRouter', () => {
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('query installations with limit = 0 and count = 1', (done) => {
|
||||
@@ -176,18 +176,18 @@ describe('InstallationsRouter', () => {
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
const rp = require('request-promise');
|
||||
const defaultHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
}
|
||||
const masterKeyHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Rest-API-Key': 'rest',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
const defaultOptions = {
|
||||
headers: defaultHeaders,
|
||||
json: true
|
||||
}
|
||||
const masterKeyOptions = {
|
||||
headers: masterKeyHeaders,
|
||||
json: true
|
||||
}
|
||||
|
||||
describe('JobSchedule', () => {
|
||||
it('should create _JobSchedule with masterKey', (done) => {
|
||||
const jobSchedule = new Parse.Object('_JobSchedule');
|
||||
jobSchedule.set({
|
||||
'jobName': 'MY Cool Job'
|
||||
});
|
||||
jobSchedule.save(null, {useMasterKey: true}).then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail creating _JobSchedule without masterKey', (done) => {
|
||||
const jobSchedule = new Parse.Object('_JobSchedule');
|
||||
jobSchedule.set({
|
||||
'jobName': 'SomeJob'
|
||||
});
|
||||
jobSchedule.save(null).then(done.fail)
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (/jobs)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs', defaultOptions).then(done.fail, done);
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (/jobs/data)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs/data', defaultOptions).then(done.fail, done);
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (PUT /jobs/id)', (done) => {
|
||||
rp.put(Parse.serverURL + '/cloud_code/jobs/jobId', defaultOptions).then(done.fail, done);
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (PUT /jobs/id)', (done) => {
|
||||
rp.del(Parse.serverURL + '/cloud_code/jobs/jobId', defaultOptions).then(done.fail, done);
|
||||
});
|
||||
|
||||
it('should allow access when using masterKey (/jobs)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should create a job schedule', (done) => {
|
||||
Parse.Cloud.job('job', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail creating a job with an invalid name', (done) => {
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options)
|
||||
.then(done.fail)
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should update a job', (done) => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
Parse.Cloud.job('job2', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].jobName).toBe('job2');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail updating a job with an invalid name', (done) => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
.then(done.fail)
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should destroy a job', (done) => {
|
||||
Parse.Cloud.job('job', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.del(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, masterKeyOptions);
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should properly return job data', (done) => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
Parse.Cloud.job('job2', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs/data', masterKeyOptions);
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.in_use).toEqual(['job1']);
|
||||
expect(res.jobs).toContain('job1');
|
||||
expect(res.jobs).toContain('job2');
|
||||
expect(res.jobs.length).toBe(2);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
+10
-10
@@ -22,9 +22,9 @@ describe('Logger', () => {
|
||||
});
|
||||
|
||||
it('should have files transports', (done) => {
|
||||
reconfigureServer().then(() => {
|
||||
let transports = logging.logger.transports;
|
||||
let transportKeys = Object.keys(transports);
|
||||
reconfigureServer().then(() => {
|
||||
const transports = logging.logger.transports;
|
||||
const transportKeys = Object.keys(transports);
|
||||
expect(transportKeys.length).toBe(3);
|
||||
done();
|
||||
});
|
||||
@@ -33,9 +33,9 @@ describe('Logger', () => {
|
||||
it('should disable files logs', (done) => {
|
||||
reconfigureServer({
|
||||
logsFolder: null
|
||||
}).then(() => {
|
||||
let transports = logging.logger.transports;
|
||||
let transportKeys = Object.keys(transports);
|
||||
}).then(() => {
|
||||
const transports = logging.logger.transports;
|
||||
const transportKeys = Object.keys(transports);
|
||||
expect(transportKeys.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
@@ -47,16 +47,16 @@ describe('Logger', () => {
|
||||
logsFolder: null,
|
||||
jsonLogs: true,
|
||||
silent: false
|
||||
}).then(() => {
|
||||
let spy = spyOn(process.stdout, 'write');
|
||||
}).then(() => {
|
||||
spyOn(process.stdout, 'write');
|
||||
logging.logger.info('hi', {key: 'value'});
|
||||
expect(process.stdout.write).toHaveBeenCalled();
|
||||
var firstLog = process.stdout.write.calls.first().args[0];
|
||||
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' })+'\n');
|
||||
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' }) + '\n');
|
||||
return reconfigureServer({
|
||||
jsonLogs: false
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('LoggerController', () => {
|
||||
loggerController.getLogs(query).then(function(res) {
|
||||
expect(res.length).not.toBe(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
@@ -74,7 +74,7 @@ describe('LoggerController', () => {
|
||||
loggerController.getLogs(query).then(function(res) {
|
||||
expect(res.length).toBe(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
@@ -84,8 +84,32 @@ describe('LoggerController', () => {
|
||||
|
||||
it('should throw without an adapter', (done) => {
|
||||
expect(() => {
|
||||
var loggerController = new LoggerController();
|
||||
new LoggerController();
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should replace implementations with verbose', (done) => {
|
||||
const adapter = new WinstonLoggerAdapter();
|
||||
const logger = new LoggerController(adapter, null, {verbose: true });
|
||||
spyOn(adapter, "log");
|
||||
logger.silly('yo!');
|
||||
expect(adapter.log).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should replace implementations with logLevel', (done) => {
|
||||
const adapter = new WinstonLoggerAdapter();
|
||||
const logger = new LoggerController(adapter, null, { logLevel: 'error' });
|
||||
spyOn(adapter, "log");
|
||||
logger.warn('yo!');
|
||||
logger.info('yo!');
|
||||
logger.debug('yo!');
|
||||
logger.verbose('yo!');
|
||||
logger.silly('yo!');
|
||||
expect(adapter.log).not.toHaveBeenCalled();
|
||||
logger.error('error');
|
||||
expect(adapter.log).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
+283
-113
@@ -3,121 +3,291 @@ var AppCache = require('../src/cache').AppCache;
|
||||
|
||||
describe('middlewares', () => {
|
||||
|
||||
var fakeReq, fakeRes;
|
||||
var fakeReq, fakeRes;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeReq = {
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: {
|
||||
_ApplicationId: 'FakeAppId'
|
||||
},
|
||||
headers: {},
|
||||
get: (key) => {
|
||||
return fakeReq.headers[key.toLowerCase()]
|
||||
}
|
||||
};
|
||||
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
|
||||
AppCache.put(fakeReq.body._ApplicationId, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
AppCache.del(fakeReq.body._ApplicationId);
|
||||
});
|
||||
|
||||
it('should use _ContentType if provided', (done) => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(undefined);
|
||||
var contentType = 'image/jpeg';
|
||||
fakeReq.body._ContentType = contentType;
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(contentType);
|
||||
expect(fakeReq.body._ContentType).toEqual(undefined);
|
||||
done()
|
||||
});
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but no key supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but different key is supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = 'clientKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
|
||||
it('should succeed when any one of the configured keys supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: 'clientKey',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when no keys are configured and none supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey'
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
const BodyParams = {
|
||||
clientVersion: '_ClientVersion',
|
||||
installationId: '_InstallationId',
|
||||
sessionToken: '_SessionToken',
|
||||
masterKey: '_MasterKey',
|
||||
javascriptKey: '_JavaScriptKey'
|
||||
beforeEach(() => {
|
||||
fakeReq = {
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: {
|
||||
_ApplicationId: 'FakeAppId'
|
||||
},
|
||||
headers: {},
|
||||
get: (key) => {
|
||||
return fakeReq.headers[key.toLowerCase()]
|
||||
}
|
||||
};
|
||||
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
|
||||
AppCache.put(fakeReq.body._ApplicationId, {});
|
||||
});
|
||||
|
||||
const BodyKeys = Object.keys(BodyParams);
|
||||
afterEach(() => {
|
||||
AppCache.del(fakeReq.body._ApplicationId);
|
||||
});
|
||||
|
||||
BodyKeys.forEach((infoKey) => {
|
||||
const bodyKey = BodyParams[infoKey];
|
||||
const keyValue = 'Fake' + bodyKey;
|
||||
// javascriptKey is the only one that gets defaulted,
|
||||
const otherKeys = BodyKeys.filter((otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey');
|
||||
|
||||
it(`it should pull ${bodyKey} into req.info`, (done) => {
|
||||
fakeReq.body[bodyKey] = keyValue;
|
||||
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.body[bodyKey]).toEqual(undefined);
|
||||
expect(fakeReq.info[infoKey]).toEqual(keyValue);
|
||||
|
||||
otherKeys.forEach((otherKey) => {
|
||||
expect(fakeReq.info[otherKey]).toEqual(undefined);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should use _ContentType if provided', (done) => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(undefined);
|
||||
var contentType = 'image/jpeg';
|
||||
fakeReq.body._ContentType = contentType;
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(contentType);
|
||||
expect(fakeReq.body._ContentType).toEqual(undefined);
|
||||
done()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but no key supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but different key is supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = 'clientKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
|
||||
it('should succeed when any one of the configured keys supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: 'clientKey',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when client key supplied but empty', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: '',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when no keys are configured and none supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey'
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
const BodyParams = {
|
||||
clientVersion: '_ClientVersion',
|
||||
installationId: '_InstallationId',
|
||||
sessionToken: '_SessionToken',
|
||||
masterKey: '_MasterKey',
|
||||
javascriptKey: '_JavaScriptKey'
|
||||
};
|
||||
|
||||
const BodyKeys = Object.keys(BodyParams);
|
||||
|
||||
BodyKeys.forEach((infoKey) => {
|
||||
const bodyKey = BodyParams[infoKey];
|
||||
const keyValue = 'Fake' + bodyKey;
|
||||
// javascriptKey is the only one that gets defaulted,
|
||||
const otherKeys = BodyKeys.filter((otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey');
|
||||
|
||||
it(`it should pull ${bodyKey} into req.info`, (done) => {
|
||||
fakeReq.body[bodyKey] = keyValue;
|
||||
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.body[bodyKey]).toEqual(undefined);
|
||||
expect(fakeReq.info[infoKey]).toEqual(keyValue);
|
||||
|
||||
otherKeys.forEach((otherKey) => {
|
||||
expect(fakeReq.info[otherKey]).toEqual(undefined);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the ip does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.ip = 'ip3';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the ip does belong to masterKeyIps list', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.ip = 'ip1';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.connection = {remoteAddress : 'ip3'};
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.connection = {remoteAddress : 'ip1'};
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.socket = {remoteAddress : 'ip3'};
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.socket = {remoteAddress : 'ip1'};
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.connection = { socket : {remoteAddress : 'ip3'}};
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
});
|
||||
fakeReq.connection = { socket : {remoteAddress : 'ip1'}};
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow any ip to use masterKey if masterKeyIps is empty', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: []
|
||||
});
|
||||
fakeReq.ip = 'ip1';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if xff header does belong to masterKeyIps', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1']
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if xff header with one ip does belong to masterKeyIps', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1']
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if xff header does not belong to masterKeyIps', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip4']
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should not succeed if xff header is empty and masterKeyIps is set', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1']
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
module.exports = options => {
|
||||
if (!options) {
|
||||
throw "Options were not provided"
|
||||
}
|
||||
let adapter = {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
if (!options) {
|
||||
throw "Options were not provided"
|
||||
}
|
||||
const adapter = {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
};
|
||||
if (options.sendMail) {
|
||||
adapter.sendMail = options.sendMail
|
||||
}
|
||||
if (options.sendPasswordResetEmail) {
|
||||
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
|
||||
}
|
||||
if (options.sendVerificationEmail) {
|
||||
adapter.sendVerificationEmail = options.sendVerificationEmail;
|
||||
}
|
||||
};
|
||||
if (options.sendMail) {
|
||||
adapter.sendMail = options.sendMail
|
||||
}
|
||||
if (options.sendPasswordResetEmail) {
|
||||
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
|
||||
}
|
||||
if (options.sendVerificationEmail) {
|
||||
adapter.sendVerificationEmail = options.sendVerificationEmail;
|
||||
}
|
||||
|
||||
return adapter;
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports = function(options) {
|
||||
return {
|
||||
options: options,
|
||||
send: function() {},
|
||||
getValidPushTypes: function() {
|
||||
return Object.keys(options.options);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -9,8 +9,8 @@ const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDataba
|
||||
describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
beforeEach(done => {
|
||||
new MongoStorageAdapter({ uri: databaseURI })
|
||||
.deleteAllClasses()
|
||||
.then(done, fail);
|
||||
.deleteAllClasses()
|
||||
.then(done, fail);
|
||||
});
|
||||
|
||||
it('auto-escapes symbols in auth information', () => {
|
||||
@@ -48,20 +48,57 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
|
||||
it('stores objectId in _id', done => {
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj._id).toEqual('abcde');
|
||||
expect(obj.objectId).toBeUndefined();
|
||||
done();
|
||||
.then(() => adapter._rawFind('Foo', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj._id).toEqual('abcde');
|
||||
expect(obj.objectId).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('find succeeds when query is within maxTimeMS', (done) => {
|
||||
const maxTimeMS = 250;
|
||||
const adapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
mongoOptions: { maxTimeMS },
|
||||
});
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS / 2})` }))
|
||||
.then(
|
||||
() => done(),
|
||||
(err) => {
|
||||
done.fail(`maxTimeMS should not affect fast queries ${err}`);
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
it('find fails when query exceeds maxTimeMS', (done) => {
|
||||
const maxTimeMS = 250;
|
||||
const adapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
mongoOptions: { maxTimeMS },
|
||||
});
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS * 2})` }))
|
||||
.then(
|
||||
() => {
|
||||
done.fail('Find succeeded despite taking too long!');
|
||||
},
|
||||
(err) => {
|
||||
expect(err.name).toEqual('MongoError');
|
||||
expect(err.code).toEqual(50);
|
||||
expect(err.message).toEqual('operation exceeded time limit');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('stores pointers with a _p_ prefix', (done) => {
|
||||
let obj = {
|
||||
const obj = {
|
||||
objectId: 'bar',
|
||||
aPointer: {
|
||||
__type: 'Pointer',
|
||||
@@ -69,52 +106,52 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
objectId: 'qwerty'
|
||||
}
|
||||
};
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('APointerDarkly', { fields: {
|
||||
objectId: { type: 'String' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
|
||||
}}, obj)
|
||||
.then(() => adapter._rawFind('APointerDarkly', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let output = results[0];
|
||||
expect(typeof output._id).toEqual('string');
|
||||
expect(typeof output._p_aPointer).toEqual('string');
|
||||
expect(output._p_aPointer).toEqual('JustThePointer$qwerty');
|
||||
expect(output.aPointer).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
.then(() => adapter._rawFind('APointerDarkly', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const output = results[0];
|
||||
expect(typeof output._id).toEqual('string');
|
||||
expect(typeof output._p_aPointer).toEqual('string');
|
||||
expect(output._p_aPointer).toEqual('JustThePointer$qwerty');
|
||||
expect(output.aPointer).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles object and subdocument', done => {
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
let schema = { fields : { subdoc: { type: 'Object' } } };
|
||||
let obj = { subdoc: {foo: 'bar', wu: 'tan'} };
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const schema = { fields : { subdoc: { type: 'Object' } } };
|
||||
const obj = { subdoc: {foo: 'bar', wu: 'tan'} };
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('tan');
|
||||
let obj = { 'subdoc.wu': 'clan' };
|
||||
return adapter.findOneAndUpdate('MyClass', schema, {}, obj);
|
||||
})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('clan');
|
||||
done();
|
||||
});
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('tan');
|
||||
const obj = { 'subdoc.wu': 'clan' };
|
||||
return adapter.findOneAndUpdate('MyClass', schema, {}, obj);
|
||||
})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('clan');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles array, object, date', (done) => {
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
let obj = {
|
||||
it('handles creating an array, object, date', (done) => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const obj = {
|
||||
array: [1, 2, 3],
|
||||
object: {foo: 'bar'},
|
||||
date: {
|
||||
@@ -122,34 +159,82 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
iso: '2016-05-26T20:55:01.154Z',
|
||||
},
|
||||
};
|
||||
let schema = { fields: {
|
||||
const schema = { fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} };
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
return adapter.find('MyClass', schema, {}, {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date.__type).toBe('Date');
|
||||
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail();
|
||||
done();
|
||||
});
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
return adapter.find('MyClass', schema, {}, {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date.__type).toBe('Date');
|
||||
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("handles updating a single object with array, object date", (done) => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = { fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} };
|
||||
|
||||
|
||||
adapter.createObject('MyClass', schema, {})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const update = {
|
||||
array: [1, 2, 3],
|
||||
object: {foo: 'bar'},
|
||||
date: {
|
||||
__type: 'Date',
|
||||
iso: '2016-05-26T20:55:01.154Z',
|
||||
},
|
||||
};
|
||||
const query = {};
|
||||
return adapter.findOneAndUpdate('MyClass', schema, query, update)
|
||||
})
|
||||
.then(results => {
|
||||
const mob = results;
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date.__type).toBe('Date');
|
||||
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
|
||||
return adapter._rawFind('MyClass', {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// These tests are unit tests designed to only test transform.js.
|
||||
"use strict";
|
||||
|
||||
let transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
|
||||
let dd = require('deep-diff');
|
||||
let mongodb = require('mongodb');
|
||||
const transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
|
||||
const dd = require('deep-diff');
|
||||
const mongodb = require('mongodb');
|
||||
|
||||
describe('parseObjectToMongoObjectForCreate', () => {
|
||||
it('a basic number', (done) => {
|
||||
@@ -145,13 +145,25 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
});
|
||||
|
||||
it('geopoint', (done) => {
|
||||
var input = {location: [180, -180]};
|
||||
var input = {location: [45, -45]};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'GeoPoint' }},
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual(
|
||||
{__type: 'GeoPoint', longitude: 180, latitude: -180}
|
||||
{__type: 'GeoPoint', longitude: 45, latitude: -45}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('polygon', (done) => {
|
||||
var input = {location: { type: 'Polygon', coordinates: [[[45, -45],[45, -45]]]}};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'Polygon' }},
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual(
|
||||
{__type: 'Polygon', coordinates: [[45, -45],[45, -45]]}
|
||||
);
|
||||
done();
|
||||
});
|
||||
@@ -179,7 +191,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
});
|
||||
|
||||
it('untransforms objects containing nested special keys', done => {
|
||||
let input = {array: [{
|
||||
const input = {array: [{
|
||||
_id: "Test ID",
|
||||
_hashed_password: "I Don't know why you would name a key this, but if you do it should work",
|
||||
_tombstone: {
|
||||
@@ -190,7 +202,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
},
|
||||
regularKey: "some data",
|
||||
}]}
|
||||
let output = transform.mongoObjectToParseObject(null, input, {
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { array: { type: 'Array' }},
|
||||
});
|
||||
expect(dd(output, input)).toEqual(undefined);
|
||||
@@ -236,6 +248,19 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('removes Relation types', (done) => {
|
||||
var input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
},
|
||||
});
|
||||
expect(output).toEqual({});
|
||||
done();
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
|
||||
var input = {
|
||||
_rperm: ['*'],
|
||||
@@ -265,7 +290,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms mongodb number types', (done) => {
|
||||
it('untransforms mongodb number types', (done) => {
|
||||
var input = {
|
||||
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
|
||||
double: new mongodb.Double(Number.MAX_VALUE)
|
||||
@@ -281,4 +306,44 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type Date', (done) => {
|
||||
var input = {
|
||||
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type String', (done) => {
|
||||
var input = {
|
||||
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformUpdate', () => {
|
||||
it('removes Relation types', (done) => {
|
||||
var input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
var output = transform.transformUpdate(null, input, {
|
||||
fields: {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
},
|
||||
});
|
||||
expect(output).toEqual({});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
var OAuth = require("../src/authDataManager/OAuth1Client");
|
||||
var request = require('request');
|
||||
var Config = require("../src/Config");
|
||||
var defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
||||
|
||||
describe('OAuth', function() {
|
||||
it("Nonce should have right length", (done) => {
|
||||
jequal(OAuth.nonce().length, 30);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString({c:1, a:2, b:3})
|
||||
jequal(string, "a=2&b=3&c=1");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build empty parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString()
|
||||
jequal(string, "");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build signature string", (done) => {
|
||||
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
|
||||
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly generate request signature", (done) => {
|
||||
var request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
};
|
||||
|
||||
var oauth_params = {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA",
|
||||
oauth_consumer_key: "hello",
|
||||
oauth_token: "token"
|
||||
};
|
||||
|
||||
var consumer_secret = "world";
|
||||
var auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build request", (done) => {
|
||||
var options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
};
|
||||
var path = "path";
|
||||
var method = "get";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
var req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
|
||||
jequal(req.host, options.host);
|
||||
jequal(req.path, "/"+path+"?query=param");
|
||||
jequal(req.method, "GET");
|
||||
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
|
||||
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
function validateCannotAuthenticateError(data, done) {
|
||||
jequal(typeof data, "object");
|
||||
jequal(typeof data.errors, "object");
|
||||
var errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var path = "/1.1/help/configuration.json";
|
||||
var params = {"lang": "en"};
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/1.1/account/settings.json";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
var options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
|
||||
it("Should validate structure of "+providerName, (done) => {
|
||||
var provider = require("../src/authDataManager/"+providerName);
|
||||
jequal(typeof provider.validateAuthData, "function");
|
||||
jequal(typeof provider.validateAppId, "function");
|
||||
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
|
||||
jequal(provider.validateAppId("app", "key", {}).constructor, Promise.prototype.constructor);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var getMockMyOauthProvider = function() {
|
||||
return {
|
||||
authData: {
|
||||
id: "12345",
|
||||
access_token: "12345",
|
||||
expiration_date: new Date().toJSON(),
|
||||
},
|
||||
shouldError: false,
|
||||
loggedOut: false,
|
||||
synchronizedUserId: null,
|
||||
synchronizedAuthToken: null,
|
||||
synchronizedExpiration: null,
|
||||
|
||||
authenticate: function(options) {
|
||||
if (this.shouldError) {
|
||||
options.error(this, "An error occurred");
|
||||
} else if (this.shouldCancel) {
|
||||
options.error(this, null);
|
||||
} else {
|
||||
options.success(this, this.authData);
|
||||
}
|
||||
},
|
||||
restoreAuthentication: function(authData) {
|
||||
if (!authData) {
|
||||
this.synchronizedUserId = null;
|
||||
this.synchronizedAuthToken = null;
|
||||
this.synchronizedExpiration = null;
|
||||
return true;
|
||||
}
|
||||
this.synchronizedUserId = authData.id;
|
||||
this.synchronizedAuthToken = authData.access_token;
|
||||
this.synchronizedExpiration = authData.expiration_date;
|
||||
return true;
|
||||
},
|
||||
getAuthType: function() {
|
||||
return "myoauth";
|
||||
},
|
||||
deauthenticate: function() {
|
||||
this.loggedOut = true;
|
||||
this.restoreAuthentication(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var ExtendedUser = Parse.User.extend({
|
||||
extended: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var createOAuthUser = function(callback) {
|
||||
var jsonBody = {
|
||||
authData: {
|
||||
myoauth: getMockMyOauthProvider().authData
|
||||
}
|
||||
};
|
||||
|
||||
var options = {
|
||||
headers: {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': 'yolo',
|
||||
'Content-Type': 'application/json' },
|
||||
url: 'http://localhost:8378/1/users',
|
||||
body: JSON.stringify(jsonBody)
|
||||
};
|
||||
|
||||
return request.post(options, callback);
|
||||
}
|
||||
|
||||
it("should create user with REST API", done => {
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
ok(b.sessionToken);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
var sessionToken = b.sessionToken;
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo('sessionToken', sessionToken);
|
||||
q.first({useMasterKey: true}).then((res) => {
|
||||
if (!res) {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
return;
|
||||
}
|
||||
expect(res.get("installationId")).toEqual('yolo');
|
||||
done();
|
||||
}).fail((err) => {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("should only create a single user with REST API", (done) => {
|
||||
var objectId;
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
objectId = b.objectId;
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
expect(b.objectId).toBe(objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("unlink and link with custom provider", (done) => {
|
||||
var provider = getMockMyOauthProvider();
|
||||
Parse.User._registerAuthenticationProvider(provider);
|
||||
Parse.User._logInWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||
|
||||
model._unlinkFrom("myoauth", {
|
||||
success: function(model) {
|
||||
|
||||
ok(!model._isLinked("myoauth"),
|
||||
"User should not be linked to myoauth");
|
||||
ok(!provider.synchronizedUserId, "User id should be cleared");
|
||||
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
|
||||
ok(!provider.synchronizedExpiration,
|
||||
"Expiration should be cleared");
|
||||
// make sure the auth data is properly deleted
|
||||
var config = new Config(Parse.applicationId);
|
||||
config.database.adapter.find('_User', {
|
||||
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
|
||||
}, { objectId: model.id }, {})
|
||||
.then(res => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0]._auth_data_myoauth).toBeUndefined();
|
||||
expect(res[0]._auth_data_myoauth).not.toBeNull();
|
||||
|
||||
model._linkWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(provider.synchronizedUserId, "User id should have a value");
|
||||
ok(provider.synchronizedAuthToken,
|
||||
"Auth token should have a value");
|
||||
ok(provider.synchronizedExpiration,
|
||||
"Expiration should have a value");
|
||||
ok(model._isLinked("myoauth"),
|
||||
"User should be linked to myoauth");
|
||||
done();
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "linking again should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "unlinking should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "linking should have worked");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
@@ -0,0 +1,146 @@
|
||||
var OAuth = require("../src/Adapters/Auth/OAuth1Client");
|
||||
|
||||
describe('OAuth', function() {
|
||||
it("Nonce should have right length", (done) => {
|
||||
jequal(OAuth.nonce().length, 30);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString({c:1, a:2, b:3})
|
||||
jequal(string, "a=2&b=3&c=1");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build empty parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString()
|
||||
jequal(string, "");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build signature string", (done) => {
|
||||
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
|
||||
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly generate request signature", (done) => {
|
||||
var request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
};
|
||||
|
||||
var oauth_params = {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA",
|
||||
oauth_consumer_key: "hello",
|
||||
oauth_token: "token"
|
||||
};
|
||||
|
||||
var consumer_secret = "world";
|
||||
var auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build request", (done) => {
|
||||
var options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
};
|
||||
var path = "path";
|
||||
var method = "get";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
var req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
|
||||
jequal(req.host, options.host);
|
||||
jequal(req.path, "/" + path + "?query=param");
|
||||
jequal(req.method, "GET");
|
||||
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
|
||||
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
function validateCannotAuthenticateError(data, done) {
|
||||
jequal(typeof data, "object");
|
||||
jequal(typeof data.errors, "object");
|
||||
var errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var path = "/1.1/help/configuration.json";
|
||||
var params = {"lang": "en"};
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/1.1/account/settings.json";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
var options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail with missing options", (done) => {
|
||||
var options = undefined;
|
||||
try {
|
||||
new OAuth(options);
|
||||
} catch (error) {
|
||||
jequal(error.message, 'No options passed to OAuth');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
+148
-121
@@ -1,23 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
let request = require('request');
|
||||
const request = require('request');
|
||||
|
||||
const delayPromise = (delay) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, delay);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Parse.Push', () => {
|
||||
var setup = function() {
|
||||
var sendToInstallationSpy = jasmine.createSpy();
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
let promises = installations.map((installation) => {
|
||||
const promises = installations.map((installation) => {
|
||||
sendToInstallationSpy(installation);
|
||||
|
||||
if (installation.deviceType == "ios") {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge+1).toEqual(installation.badge);
|
||||
expect(installation.originalBadge + 1).toEqual(installation.badge);
|
||||
} else {
|
||||
expect(installation.badge).toBeUndefined();
|
||||
}
|
||||
return Promise.resolve({
|
||||
err: null,
|
||||
deviceType: installation.deviceType,
|
||||
result: true
|
||||
device: installation,
|
||||
transmitted: true
|
||||
})
|
||||
});
|
||||
return Promise.all(promises);
|
||||
@@ -35,128 +45,58 @@ describe('Parse.Push', () => {
|
||||
adapter: pushAdapter
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_"+installations.length);
|
||||
installation.set("deviceToken","device_token_"+installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return {
|
||||
sendToInstallationSpy,
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
it('should properly send push', (done) => {
|
||||
return setup().then(() => {
|
||||
return setup().then(({ sendToInstallationSpy }) => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'Increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
})
|
||||
.then(() => {
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'Increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
.then(() => {
|
||||
return delayPromise(500);
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendToInstallationSpy.calls.count()).toEqual(10);
|
||||
})
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly send push with lowercaseIncrement', (done) => {
|
||||
return setup().then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow clients to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow master key to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
try {
|
||||
expect(body.results.length).toEqual(1);
|
||||
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
|
||||
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
|
||||
} catch(e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if missing push configuration', done => {
|
||||
reconfigureServer({push: null})
|
||||
.then(() => {
|
||||
return setup().then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
@@ -166,14 +106,101 @@ describe('Parse.Push', () => {
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then((response) => {
|
||||
fail('should not succeed');
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
|
||||
}).then(() => {
|
||||
return delayPromise(500);
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow clients to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() => delayPromise(500))
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow master key to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() => delayPromise(500)) // put a delay as we keep writing
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
try {
|
||||
expect(body.results.length).toEqual(1);
|
||||
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
|
||||
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
|
||||
} catch(e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if missing push configuration', done => {
|
||||
reconfigureServer({push: null})
|
||||
.then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+468
-435
File diff suppressed because it is too large
Load Diff
+381
-383
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
|
||||
it('can initialize', function() {
|
||||
var config = {}
|
||||
var publisher = new ParseCloudCodePublisher(config);
|
||||
new ParseCloudCodePublisher(config);
|
||||
|
||||
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
|
||||
@@ -32,7 +32,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
var request = {};
|
||||
publisher.onCloudCodeAfterSave(request);
|
||||
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith('afterSave', request);
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterSave', request);
|
||||
});
|
||||
|
||||
it('can handle cloud code afterDelete request', function() {
|
||||
@@ -41,7 +41,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
var request = {};
|
||||
publisher.onCloudCodeAfterDelete(request);
|
||||
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith('afterDelete', request);
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterDelete', request);
|
||||
});
|
||||
|
||||
it('can handle cloud code request', function() {
|
||||
|
||||
+294
-8
@@ -113,7 +113,7 @@ describe('Parse.File testing', () => {
|
||||
'X-Parse-Master-Key': 'test'
|
||||
},
|
||||
url: 'http://localhost:8378/1/files/' + b.name
|
||||
}, (error, response, body) => {
|
||||
}, (error, response) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toEqual(200);
|
||||
request.get({
|
||||
@@ -122,7 +122,7 @@ describe('Parse.File testing', () => {
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
},
|
||||
url: b.url
|
||||
}, (error, response, body) => {
|
||||
}, (error, response) => {
|
||||
expect(error).toBe(null);
|
||||
try {
|
||||
expect(response.statusCode).toEqual(404);
|
||||
@@ -370,7 +370,7 @@ describe('Parse.File testing', () => {
|
||||
object.save({
|
||||
file: file
|
||||
}, expectSuccess({
|
||||
success: function(obj) {
|
||||
success: function() {
|
||||
ok(object.toJSON().file.url);
|
||||
done();
|
||||
}
|
||||
@@ -391,7 +391,7 @@ describe('Parse.File testing', () => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.name).toMatch(/\.html$/);
|
||||
request.get(b.url, (error, response, body) => {
|
||||
request.get(b.url, (error, response) => {
|
||||
if (!response) {
|
||||
fail('response should be set');
|
||||
return done();
|
||||
@@ -563,7 +563,7 @@ describe('Parse.File testing', () => {
|
||||
var query = new Parse.Query('FileTest');
|
||||
return query.first();
|
||||
}).then(result => {
|
||||
let fileAgain = result.get('file');
|
||||
const fileAgain = result.get('file');
|
||||
expect(fileAgain.url()).toMatch(/123.txt$/);
|
||||
done();
|
||||
}).catch((e) => {
|
||||
@@ -572,10 +572,10 @@ describe('Parse.File testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('return with publicServerURL when provided', done => {
|
||||
it('return with publicServerURL when provided', done => {
|
||||
reconfigureServer({
|
||||
publicServerURL: 'https://mydomain/parse'
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
var file = {
|
||||
__type: 'File',
|
||||
name: '123.txt'
|
||||
@@ -587,7 +587,7 @@ describe('Parse.File testing', () => {
|
||||
var query = new Parse.Query('FileTest');
|
||||
return query.first();
|
||||
}).then(result => {
|
||||
let fileAgain = result.get('file');
|
||||
const fileAgain = result.get('file');
|
||||
expect(fileAgain.url().indexOf('https://mydomain/parse')).toBe(0);
|
||||
done();
|
||||
}).catch((e) => {
|
||||
@@ -595,4 +595,290 @@ describe('Parse.File testing', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to upload an empty file', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: '',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(body).toEqual('{"code":130,"error":"Invalid file upload."}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to upload without a file name', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/',
|
||||
body: 'yolo',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(body).toEqual('{"code":122,"error":"Filename not provided."}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to upload without a file name', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/',
|
||||
body: 'yolo',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(body).toEqual('{"code":122,"error":"Filename not provided."}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to delete an unkown file', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
};
|
||||
request.delete({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(body).toEqual('{"code":153,"error":"Could not delete file."}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('mongo')('Gridstore Range tests', () => {
|
||||
it('supports range requests', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: 'argle bargle',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=0-5'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body).toEqual('argle ');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('supports small range requests', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: 'argle bargle',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=0-2'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body).toEqual('arg');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// See specs https://www.greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#byte.ranges
|
||||
it('supports getting one byte', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: 'argle bargle',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=2-2'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body).toEqual('g');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('supports getting last n bytes', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: 'something different',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=-4'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body.length).toBe(4);
|
||||
expect(body).toEqual('rent');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('supports getting first n bytes', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: 'something different',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=10-'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body).toEqual('different');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function repeat(string, count) {
|
||||
var s = string;
|
||||
while (count > 0) {
|
||||
s += string;
|
||||
count--;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
it('supports large range requests', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: repeat('argle bargle', 100)
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=13-240'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body.length).toEqual(228);
|
||||
expect(body.indexOf('rgle barglea')).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to stream unknown file', done => {
|
||||
request.get({ url: 'http://localhost:8378/1/files/test/file.txt', headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=13-240'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toBe(404);
|
||||
expect(body).toEqual('File not found.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Because GridStore is not loaded on PG, those are perfect
|
||||
// for fallback tests
|
||||
describe_only_db('postgres')('Default Range tests', () => {
|
||||
it('fallback to regular request', done => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/files/file.txt',
|
||||
body: 'argle bargle',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
request.get({ url: b.url, headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Range': 'bytes=0-5'
|
||||
} }, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
expect(body).toEqual('argle bargle');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+317
-21
@@ -1,9 +1,11 @@
|
||||
// This is a port of the test suite:
|
||||
// hungry/js/test/parse_geo_point_test.js
|
||||
|
||||
const rp = require('request-promise');
|
||||
var TestObject = Parse.Object.extend('TestObject');
|
||||
|
||||
describe('Parse.GeoPoint testing', () => {
|
||||
|
||||
it('geo point roundtrip', (done) => {
|
||||
var point = new Parse.GeoPoint(44.0, -11.0);
|
||||
var obj = new TestObject();
|
||||
@@ -26,6 +28,47 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('update geopoint', (done) => {
|
||||
const oldPoint = new Parse.GeoPoint(44.0, -11.0);
|
||||
const newPoint = new Parse.GeoPoint(24.0, 19.0);
|
||||
const obj = new TestObject();
|
||||
obj.set('location', oldPoint);
|
||||
obj.save().then(() => {
|
||||
obj.set('location', newPoint);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
var query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const point = result.get('location');
|
||||
equal(point.latitude, newPoint.latitude);
|
||||
equal(point.longitude, newPoint.longitude);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has the correct __type field in the json response', done => {
|
||||
var point = new Parse.GeoPoint(44.0, -11.0);
|
||||
var obj = new TestObject();
|
||||
obj.set('location', point);
|
||||
obj.set('name', 'Zhoul')
|
||||
obj.save(null, {
|
||||
success: (obj) => {
|
||||
Parse.Cloud.httpRequest({
|
||||
url: 'http://localhost:8378/1/classes/TestObject/' + obj.id,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
}).then(response => {
|
||||
equal(response.data.location.__type, 'GeoPoint');
|
||||
done();
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('geo point exception two fields', (done) => {
|
||||
var point = new Parse.GeoPoint(20, 20);
|
||||
var obj = new TestObject();
|
||||
@@ -76,7 +119,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('index', i);
|
||||
objects.push(obj);
|
||||
});
|
||||
Parse.Object.saveAll(objects).then((list) => {
|
||||
Parse.Object.saveAll(objects).then(() => {
|
||||
var query = new Parse.Query(TestObject);
|
||||
var point = new Parse.GeoPoint(1.0, -1.0);
|
||||
query.withinRadians('location', point, 3.14);
|
||||
@@ -99,7 +142,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('index', i);
|
||||
objects.push(obj);
|
||||
});
|
||||
Parse.Object.saveAll(objects, function(list) {
|
||||
Parse.Object.saveAll(objects, function() {
|
||||
var query = new Parse.Query(TestObject);
|
||||
var point = new Parse.GeoPoint(1.0, -1.0);
|
||||
query.withinRadians('location', point, 3.14 * 0.5);
|
||||
@@ -123,7 +166,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('index', i);
|
||||
objects.push(obj);
|
||||
});
|
||||
Parse.Object.saveAll(objects, function(list) {
|
||||
Parse.Object.saveAll(objects, function() {
|
||||
var query = new Parse.Query(TestObject);
|
||||
var point = new Parse.GeoPoint(1.0, -1.0);
|
||||
query.withinRadians('location', point, 3.14 * 0.25);
|
||||
@@ -154,7 +197,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
};
|
||||
|
||||
it('geo max distance in km everywhere', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
// Honolulu is 4300 km away from SFO on a sphere ;)
|
||||
@@ -169,7 +212,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in km california', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 3700.0);
|
||||
@@ -185,7 +228,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in km bay area', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 100.0);
|
||||
@@ -200,7 +243,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in km mid peninsula', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 10.0);
|
||||
@@ -214,7 +257,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles everywhere', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 2600.0);
|
||||
@@ -228,7 +271,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles california', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 2200.0);
|
||||
@@ -244,7 +287,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles bay area', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
// 100km is 62 miles...
|
||||
@@ -260,7 +303,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles mid peninsula', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 10.0);
|
||||
@@ -273,19 +316,36 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works with geobox queries', (done) => {
|
||||
var inSF = new Parse.GeoPoint(37.75, -122.4);
|
||||
var southwestOfSF = new Parse.GeoPoint(37.708813, -122.526398);
|
||||
var northeastOfSF = new Parse.GeoPoint(37.822802, -122.373962);
|
||||
|
||||
var object = new TestObject();
|
||||
object.set('point', inSF);
|
||||
object.save().then(() => {
|
||||
it('returns nearest location', (done) => {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinGeoBox('point', southwestOfSF, northeastOfSF);
|
||||
query.near('location', sfo);
|
||||
query.find({
|
||||
success: function(results) {
|
||||
equal(results[0].get('name'), 'San Francisco');
|
||||
equal(results[1].get('name'), 'Sacramento');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('works with geobox queries', (done) => {
|
||||
const inbound = new Parse.GeoPoint(1.5, 1.5);
|
||||
const onbound = new Parse.GeoPoint(10, 10);
|
||||
const outbound = new Parse.GeoPoint(20, 20);
|
||||
const obj1 = new Parse.Object('TestObject', {location: inbound});
|
||||
const obj2 = new Parse.Object('TestObject', {location: onbound});
|
||||
const obj3 = new Parse.Object('TestObject', {location: outbound});
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const sw = new Parse.GeoPoint(0, 0);
|
||||
const ne = new Parse.GeoPoint(10, 10);
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.withinGeoBox('location', sw, ne);
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
equal(results.length, 1);
|
||||
equal(results.length, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -332,4 +392,240 @@ describe('Parse.GeoPoint testing', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('equalTo geopoint', (done) => {
|
||||
var point = new Parse.GeoPoint(44.0, -11.0);
|
||||
var obj = new TestObject();
|
||||
obj.set('location', point);
|
||||
obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('location', point);
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
equal(results.length, 1);
|
||||
const loc = results[0].get('location');
|
||||
equal(loc.latitude, point.latitude);
|
||||
equal(loc.longitude, point.longitude);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('supports withinPolygon open path', (done) => {
|
||||
const inbound = new Parse.GeoPoint(1.5, 1.5);
|
||||
const onbound = new Parse.GeoPoint(10, 10);
|
||||
const outbound = new Parse.GeoPoint(20, 20);
|
||||
const obj1 = new Parse.Object('Polygon', {location: inbound});
|
||||
const obj2 = new Parse.Object('Polygon', {location: onbound});
|
||||
const obj3 = new Parse.Object('Polygon', {location: outbound});
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: [
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 10 },
|
||||
{ __type: 'GeoPoint', latitude: 10, longitude: 10 },
|
||||
{ __type: 'GeoPoint', latitude: 10, longitude: 0 }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('supports withinPolygon closed path', (done) => {
|
||||
const inbound = new Parse.GeoPoint(1.5, 1.5);
|
||||
const onbound = new Parse.GeoPoint(10, 10);
|
||||
const outbound = new Parse.GeoPoint(20, 20);
|
||||
const obj1 = new Parse.Object('Polygon', {location: inbound});
|
||||
const obj2 = new Parse.Object('Polygon', {location: onbound});
|
||||
const obj3 = new Parse.Object('Polygon', {location: outbound});
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: [
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 10 },
|
||||
{ __type: 'GeoPoint', latitude: 10, longitude: 10 },
|
||||
{ __type: 'GeoPoint', latitude: 10, longitude: 0 },
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('invalid input withinPolygon', (done) => {
|
||||
const point = new Parse.GeoPoint(1.5, 1.5);
|
||||
const obj = new Parse.Object('Polygon', {location: point});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: 1234
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid geoPoint withinPolygon', (done) => {
|
||||
const point = new Parse.GeoPoint(1.5, 1.5);
|
||||
const obj = new Parse.Object('Polygon', {location: point});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: [
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid latitude withinPolygon', (done) => {
|
||||
const point = new Parse.GeoPoint(1.5, 1.5);
|
||||
const obj = new Parse.Object('Polygon', {location: point});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: [
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
|
||||
{ __type: 'GeoPoint', latitude: 181, longitude: 0 },
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid longitude withinPolygon', (done) => {
|
||||
const point = new Parse.GeoPoint(1.5, 1.5);
|
||||
const obj = new Parse.Object('Polygon', {location: point});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: [
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 181 },
|
||||
{ __type: 'GeoPoint', latitude: 0, longitude: 0 }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('minimum 3 points withinPolygon', (done) => {
|
||||
const point = new Parse.GeoPoint(1.5, 1.5);
|
||||
const obj = new Parse.Object('Polygon', {location: point});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoWithin: {
|
||||
$polygon: []
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/Polygon',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(107);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var request = require('request');
|
||||
var Parse = require('parse/node').Parse;
|
||||
let Config = require('../src/Config');
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe('a GlobalConfig', () => {
|
||||
beforeEach(done => {
|
||||
let config = new Config('test');
|
||||
let query = on_db('mongo', () => {
|
||||
const config = new Config('test');
|
||||
const query = on_db('mongo', () => {
|
||||
// Legacy is with an int...
|
||||
return { objectId: 1 };
|
||||
}, () => {
|
||||
@@ -18,7 +17,7 @@ describe('a GlobalConfig', () => {
|
||||
{ fields: { objectId: { type: 'Number' }, params: {type: 'Object'}} },
|
||||
query,
|
||||
{ params: { companies: ['US', 'DK'] } }
|
||||
).then(done, (err) => {
|
||||
).then(done, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
@@ -57,6 +56,49 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive files', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { file: { __type: 'File', name: 'name', url: 'http://url' } } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
const file = res.get('file');
|
||||
expect(file.name()).toBe('name');
|
||||
expect(file.url()).toBe('http://url');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive Geopoints', (done) => {
|
||||
const geopoint = new Parse.GeoPoint(10,-20);
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { point: geopoint.toJSON() } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
const point = res.get('point');
|
||||
expect(point.latitude).toBe(10);
|
||||
expect(point.longitude).toBe(-20);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('properly handles delete op', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
@@ -105,7 +147,7 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
|
||||
it('failed getting config when it is missing', (done) => {
|
||||
let config = new Config('test');
|
||||
const config = new Config('test');
|
||||
config.database.adapter.deleteObjectsByQuery(
|
||||
'_GlobalConfig',
|
||||
{ fields: { params: { __type: 'String' } } },
|
||||
@@ -123,7 +165,7 @@ describe('a GlobalConfig', () => {
|
||||
expect(body.params).toEqual({});
|
||||
done();
|
||||
});
|
||||
}).catch((e) => {
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
|
||||
+417
-416
@@ -7,147 +7,149 @@ var express = require("express");
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
var port = 12345;
|
||||
var hookServerURL = "http://localhost:"+port;
|
||||
let AppCache = require('../src/cache').AppCache;
|
||||
var hookServerURL = "http://localhost:" + port;
|
||||
const AppCache = require('../src/cache').AppCache;
|
||||
|
||||
var app = express();
|
||||
app.use(bodyParser.json({ 'type': '*/*' }))
|
||||
app.listen(12345);
|
||||
|
||||
describe('Hooks', () => {
|
||||
it("should have no hooks registered", (done) => {
|
||||
Parse.Hooks.getFunctions().then((res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should have no hooks registered", (done) => {
|
||||
Parse.Hooks.getFunctions().then((res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should have no triggers registered", (done) => {
|
||||
Parse.Hooks.getTriggers().then( (res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should have no triggers registered", (done) => {
|
||||
Parse.Hooks.getTriggers().then((res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should CRUD a function registration", (done) => {
|
||||
// Create
|
||||
Parse.Hooks.createFunction("My-Test-Function", "http://someurl")
|
||||
.then(response => {
|
||||
expect(response.functionName).toBe("My-Test-Function");
|
||||
expect(response.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getFunction("My-Test-Function")
|
||||
}).then(response => {
|
||||
expect(response.objectId).toBeUndefined();
|
||||
expect(response.url).toBe("http://someurl");
|
||||
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.objectId).toBeUndefined();
|
||||
expect(res.functionName).toBe("My-Test-Function");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
// delete
|
||||
return Parse.Hooks.removeFunction("My-Test-Function")
|
||||
})
|
||||
.then((res) => {
|
||||
.then(response => {
|
||||
expect(response.functionName).toBe("My-Test-Function");
|
||||
expect(response.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getFunction("My-Test-Function")
|
||||
}).then(response => {
|
||||
expect(response.objectId).toBeUndefined();
|
||||
expect(response.url).toBe("http://someurl");
|
||||
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.objectId).toBeUndefined();
|
||||
expect(res.functionName).toBe("My-Test-Function");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
// delete
|
||||
return Parse.Hooks.removeFunction("My-Test-Function")
|
||||
})
|
||||
.then(() => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getFunction("My-Test-Function")
|
||||
.then(res => {
|
||||
fail("Failed to delete hook")
|
||||
fail(res)
|
||||
return Parse.Hooks.getFunction("My-Test-Function")
|
||||
.then(res => {
|
||||
fail("Failed to delete hook")
|
||||
fail(res)
|
||||
done();
|
||||
return Promise.resolve();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe("no function named: My-Test-Function is defined")
|
||||
done();
|
||||
return Promise.resolve();
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
return Promise.resolve();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe("no function named: My-Test-Function is defined")
|
||||
done();
|
||||
return Promise.resolve();
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should CRUD a trigger registration", (done) => {
|
||||
// Create
|
||||
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.triggerName).toBe("beforeDelete");
|
||||
expect(res.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res).not.toBe(null);
|
||||
expect(res).not.toBe(undefined);
|
||||
expect(res.objectId).toBeUndefined();
|
||||
expect(res.url).toBe("http://someurl");
|
||||
// delete
|
||||
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
expect(res.objectId).toBeUndefined();
|
||||
// Create
|
||||
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.triggerName).toBe("beforeDelete");
|
||||
expect(res.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res).not.toBe(null);
|
||||
expect(res).not.toBe(undefined);
|
||||
expect(res.objectId).toBeUndefined();
|
||||
expect(res.url).toBe("http://someurl");
|
||||
// delete
|
||||
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
expect(res.objectId).toBeUndefined();
|
||||
|
||||
return Parse.Hooks.removeTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, (err) => {
|
||||
return Parse.Hooks.removeTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then(() => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
expect(err).not.toBe(null);
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe("class MyClass does not exist")
|
||||
} else {
|
||||
} else {
|
||||
fail('should have errored');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to register hooks without Master Key", (done) => {
|
||||
request.post(Parse.serverURL+"/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-REST-API-Key": Parse.restKey,
|
||||
},
|
||||
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("unauthorized");
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("should fail to register hooks without Master Key", (done) => {
|
||||
request.post(Parse.serverURL + "/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-REST-API-Key": Parse.restKey,
|
||||
},
|
||||
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("unauthorized");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to create two times the same function", (done) => {
|
||||
Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => {
|
||||
it("should fail trying to create two times the same function", (done) => {
|
||||
Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||
.then(() => {
|
||||
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new function");
|
||||
}).then( () => {
|
||||
}).then(() => {
|
||||
fail("should not be able to create the same function");
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
@@ -163,185 +165,185 @@ describe('Hooks', () => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to create two times the same trigger", (done) => {
|
||||
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => {
|
||||
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new trigger");
|
||||
}).then( () => {
|
||||
fail("should not be able to create the same trigger");
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class MyClass already has trigger beforeSave')
|
||||
}
|
||||
return Parse.Hooks.removeTrigger("MyClass", "beforeSave");
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("should fail trying to create two times the same trigger", (done) => {
|
||||
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then(() => {
|
||||
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new trigger");
|
||||
}).then(() => {
|
||||
fail("should not be able to create the same trigger");
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class MyClass already has trigger beforeSave')
|
||||
}
|
||||
return Parse.Hooks.removeTrigger("MyClass", "beforeSave");
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to update a function that don't exist", (done) => {
|
||||
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
}
|
||||
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
|
||||
}).then( (res) => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should fail trying to update a function that don't exist", (done) => {
|
||||
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then(() => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
}
|
||||
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
|
||||
}).then(() => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to update a trigger that don't exist", (done) => {
|
||||
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class AClassName does not exist');
|
||||
}
|
||||
return Parse.Hooks.getTrigger("AClassName","beforeSave")
|
||||
}).then( (res) => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class AClassName does not exist');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should fail trying to update a trigger that don't exist", (done) => {
|
||||
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then(() => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class AClassName does not exist');
|
||||
}
|
||||
return Parse.Hooks.getTrigger("AClassName","beforeSave")
|
||||
}).then(() => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class AClassName does not exist');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should fail trying to create a malformed function", (done) => {
|
||||
Parse.Hooks.createFunction("MyFunction").then( (res) => {
|
||||
fail(res);
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe("invalid hook declaration");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should fail trying to create a malformed function", (done) => {
|
||||
Parse.Hooks.createFunction("MyFunction").then((res) => {
|
||||
fail(res);
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe("invalid hook declaration");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to create a malformed function (REST)", (done) => {
|
||||
request.post(Parse.serverURL+"/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-Master-Key": Parse.masterKey,
|
||||
},
|
||||
body: JSON.stringify({ functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("invalid hook declaration");
|
||||
expect(body.code).toBe(143);
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("should fail trying to create a malformed function (REST)", (done) => {
|
||||
request.post(Parse.serverURL + "/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-Master-Key": Parse.masterKey,
|
||||
},
|
||||
body: JSON.stringify({ functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("invalid hook declaration");
|
||||
expect(body.code).toBe(143);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should create hooks and properly preload them", (done) => {
|
||||
it("should create hooks and properly preload them", (done) => {
|
||||
|
||||
var promises = [];
|
||||
for (var i = 0; i<5; i++) {
|
||||
promises.push(Parse.Hooks.createTrigger("MyClass"+i, "beforeSave", "http://url.com/beforeSave/"+i));
|
||||
promises.push(Parse.Hooks.createFunction("AFunction"+i, "http://url.com/function"+i));
|
||||
}
|
||||
var promises = [];
|
||||
for (var i = 0; i < 5; i++) {
|
||||
promises.push(Parse.Hooks.createTrigger("MyClass" + i, "beforeSave", "http://url.com/beforeSave/" + i));
|
||||
promises.push(Parse.Hooks.createFunction("AFunction" + i, "http://url.com/function" + i));
|
||||
}
|
||||
|
||||
Parse.Promise.when(promises).then(function(results){
|
||||
for (var i=0; i<5; i++) {
|
||||
// Delete everything from memory, as the server just started
|
||||
triggers.removeTrigger("beforeSave", "MyClass"+i, Parse.applicationId);
|
||||
triggers.removeFunction("AFunction"+i, Parse.applicationId);
|
||||
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).toBeUndefined();
|
||||
}
|
||||
const hooksController = new HooksController(Parse.applicationId, AppCache.get('test').databaseController);
|
||||
return hooksController.load()
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail('Should properly create all hooks');
|
||||
done();
|
||||
}).then(function() {
|
||||
for (var i=0; i<5; i++) {
|
||||
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).not.toBeUndefined();
|
||||
}
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail('should properly load all hooks');
|
||||
done();
|
||||
})
|
||||
});
|
||||
Parse.Promise.when(promises).then(function(){
|
||||
for (var i = 0; i < 5; i++) {
|
||||
// Delete everything from memory, as the server just started
|
||||
triggers.removeTrigger("beforeSave", "MyClass" + i, Parse.applicationId);
|
||||
triggers.removeFunction("AFunction" + i, Parse.applicationId);
|
||||
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).toBeUndefined();
|
||||
}
|
||||
const hooksController = new HooksController(Parse.applicationId, AppCache.get('test').databaseController);
|
||||
return hooksController.load()
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail('Should properly create all hooks');
|
||||
done();
|
||||
}).then(function() {
|
||||
for (var i = 0; i < 5; i++) {
|
||||
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).not.toBeUndefined();
|
||||
}
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail('should properly load all hooks');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should run the function on the test server", (done) => {
|
||||
it("should run the function on the test server", (done) => {
|
||||
|
||||
app.post("/SomeFunction", function(req, res) {
|
||||
res.json({success:"OK!"});
|
||||
});
|
||||
app.post("/SomeFunction", function(req, res) {
|
||||
res.json({success:"OK!"});
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("OK!");
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunction").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("OK!");
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should run the function on the test server", (done) => {
|
||||
it("should run the function on the test server", (done) => {
|
||||
|
||||
app.post("/SomeFunctionError", function(req, res) {
|
||||
res.json({error: {code: 1337, error: "hacking that one!"}});
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunctionError").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
app.post("/SomeFunctionError", function(req, res) {
|
||||
res.json({error: {code: 1337, error: "hacking that one!"}});
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunctionError").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function() {
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
@@ -350,148 +352,147 @@ describe('Hooks', () => {
|
||||
expect(err.message.error).toEqual("hacking that one!");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should provide X-Parse-Webhook-Key when defined", (done) => {
|
||||
app.post("/ExpectingKey", function(req, res) {
|
||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||
res.json({success: "correct key provided"});
|
||||
} else {
|
||||
res.json({error: "incorrect key provided"});
|
||||
}
|
||||
});
|
||||
it("should provide X-Parse-Webhook-Key when defined", (done) => {
|
||||
app.post("/ExpectingKey", function(req, res) {
|
||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||
res.json({success: "correct key provided"});
|
||||
} else {
|
||||
res.json({error: "incorrect key provided"});
|
||||
}
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKey").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("correct key provided");
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKey").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("correct key provided");
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
|
||||
reconfigureServer({ webhookKey: undefined })
|
||||
.then(() => {
|
||||
app.post("/ExpectingKeyAlso", function(req, res) {
|
||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||
res.json({success: "correct key provided"});
|
||||
} else {
|
||||
res.json({error: "incorrect key provided"});
|
||||
}
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKeyAlso").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(141);
|
||||
expect(err.message).toEqual("incorrect key provided");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should run the beforeSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
app.post("/BeforeSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var object = req.body.object;
|
||||
object.hello = "world";
|
||||
// Would need parse cloud express to set much more
|
||||
// But this should override the key upon return
|
||||
res.json({success: object});
|
||||
});
|
||||
// The function is delete as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave" ,hookServerURL+"/BeforeSaveSome").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function(res) {
|
||||
expect(triggerCount).toBe(1);
|
||||
return res.fetch();
|
||||
}).then(function(res) {
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("beforeSave hooks should correctly handle responses containing entire object", (done) => {
|
||||
app.post("/BeforeSaveSome2", function(req, res) {
|
||||
var object = Parse.Object.fromJSON(req.body.object);
|
||||
object.set('hello', "world");
|
||||
res.json({success: object});
|
||||
});
|
||||
Parse.Hooks.createTrigger("SomeRandomObject2", "beforeSave" ,hookServerURL+"/BeforeSaveSome2").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject2");
|
||||
return obj.save();
|
||||
}).then(function(res) {
|
||||
return res.save();
|
||||
}).then(function(res) {
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
fail(`Should not fail: ${JSON.stringify(err)}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should run the afterSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
var newObjectId;
|
||||
app.post("/AfterSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var obj = new Parse.Object("AnotherObject");
|
||||
obj.set("foo", "bar");
|
||||
obj.save().then(function(obj){
|
||||
newObjectId = obj.id;
|
||||
res.json({success: {}});
|
||||
})
|
||||
});
|
||||
// The function is delete as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave" ,hookServerURL+"/AfterSaveSome").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function(res){
|
||||
var promise = new Parse.Promise();
|
||||
// Wait a bit here as it's an after save
|
||||
setTimeout(function(){
|
||||
expect(triggerCount).toBe(1);
|
||||
var q = new Parse.Query("AnotherObject");
|
||||
q.get(newObjectId).then(function(r){
|
||||
promise.resolve(r);
|
||||
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
|
||||
reconfigureServer({ webhookKey: undefined })
|
||||
.then(() => {
|
||||
app.post("/ExpectingKeyAlso", function(req, res) {
|
||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||
res.json({success: "correct key provided"});
|
||||
} else {
|
||||
res.json({error: "incorrect key provided"});
|
||||
}
|
||||
});
|
||||
}, 300)
|
||||
return promise;
|
||||
}).then(function(res){
|
||||
expect(res.get("foo")).toEqual("bar");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKeyAlso").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(){
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(141);
|
||||
expect(err.message).toEqual("incorrect key provided");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should run the beforeSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
app.post("/BeforeSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var object = req.body.object;
|
||||
object.hello = "world";
|
||||
// Would need parse cloud express to set much more
|
||||
// But this should override the key upon return
|
||||
res.json({success: object});
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave", hookServerURL + "/BeforeSaveSome").then(function () {
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function(res) {
|
||||
expect(triggerCount).toBe(1);
|
||||
return res.fetch();
|
||||
}).then(function(res) {
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("beforeSave hooks should correctly handle responses containing entire object", (done) => {
|
||||
app.post("/BeforeSaveSome2", function(req, res) {
|
||||
var object = Parse.Object.fromJSON(req.body.object);
|
||||
object.set('hello', "world");
|
||||
res.json({success: object});
|
||||
});
|
||||
Parse.Hooks.createTrigger("SomeRandomObject2", "beforeSave", hookServerURL + "/BeforeSaveSome2").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject2");
|
||||
return obj.save();
|
||||
}).then(function(res) {
|
||||
return res.save();
|
||||
}).then(function(res) {
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
fail(`Should not fail: ${JSON.stringify(err)}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should run the afterSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
var newObjectId;
|
||||
app.post("/AfterSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var obj = new Parse.Object("AnotherObject");
|
||||
obj.set("foo", "bar");
|
||||
obj.save().then(function(obj){
|
||||
newObjectId = obj.id;
|
||||
res.json({success: {}});
|
||||
})
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave", hookServerURL + "/AfterSaveSome").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function() {
|
||||
var promise = new Parse.Promise();
|
||||
// Wait a bit here as it's an after save
|
||||
setTimeout(() => {
|
||||
expect(triggerCount).toBe(1);
|
||||
new Parse.Query("AnotherObject")
|
||||
.get(newObjectId)
|
||||
.then((r) => promise.resolve(r));
|
||||
}, 500);
|
||||
return promise;
|
||||
}).then(function(res){
|
||||
expect(res.get("foo")).toEqual("bar");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+576
-571
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer');
|
||||
jasmine.mockLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer', mockParseWebSocketServer);
|
||||
// Mock Client
|
||||
var mockClient = function() {
|
||||
var mockClient = function(id, socket, hasMasterKey) {
|
||||
this.pushConnect = jasmine.createSpy('pushConnect');
|
||||
this.pushSubscribe = jasmine.createSpy('pushSubscribe');
|
||||
this.pushUnsubscribe = jasmine.createSpy('pushUnsubscribe');
|
||||
@@ -24,6 +24,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
this.addSubscriptionInfo = jasmine.createSpy('addSubscriptionInfo');
|
||||
this.getSubscriptionInfo = jasmine.createSpy('getSubscriptionInfo');
|
||||
this.deleteSubscriptionInfo = jasmine.createSpy('deleteSubscriptionInfo');
|
||||
this.hasMasterKey = hasMasterKey;
|
||||
}
|
||||
mockClient.pushError = jasmine.createSpy('pushError');
|
||||
jasmine.mockLibrary('../src/LiveQuery/Client', 'Client', mockClient);
|
||||
@@ -80,7 +81,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var httpServer = {};
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, httpServer);
|
||||
|
||||
expect(parseLiveQueryServer.clientId).toBe(0);
|
||||
expect(parseLiveQueryServer.clientId).toBeUndefined();
|
||||
expect(parseLiveQueryServer.clients.size).toBe(0);
|
||||
expect(parseLiveQueryServer.subscriptions.size).toBe(0);
|
||||
});
|
||||
@@ -93,9 +94,11 @@ describe('ParseLiveQueryServer', function() {
|
||||
parseLiveQueryServer._validateKeys = jasmine.createSpy('validateKeys').and.returnValue(true);
|
||||
parseLiveQueryServer._handleConnect(parseWebSocket);
|
||||
|
||||
expect(parseLiveQueryServer.clientId).toBe(1);
|
||||
expect(parseWebSocket.clientId).toBe(0);
|
||||
var client = parseLiveQueryServer.clients.get(0);
|
||||
const clientKeys = parseLiveQueryServer.clients.keys();
|
||||
expect(parseLiveQueryServer.clients.size).toBe(1);
|
||||
const firstKey = clientKeys.next().value;
|
||||
expect(parseWebSocket.clientId).toBe(firstKey);
|
||||
var client = parseLiveQueryServer.clients.get(firstKey);
|
||||
expect(client).not.toBeNull();
|
||||
// Make sure we send connect response to the client
|
||||
expect(client.pushConnect).toHaveBeenCalled();
|
||||
@@ -159,7 +162,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Add two mock clients
|
||||
var clientId = 1;
|
||||
var client = addMockClient(parseLiveQueryServer, clientId);
|
||||
addMockClient(parseLiveQueryServer, clientId);
|
||||
var clientIdAgain = 2;
|
||||
var clientAgain = addMockClient(parseLiveQueryServer, clientIdAgain);
|
||||
// Add subscription for mock client 1
|
||||
@@ -232,7 +235,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Add mock client
|
||||
var clientId = 1;
|
||||
var client = addMockClient(parseLiveQueryServer, clientId);
|
||||
addMockClient(parseLiveQueryServer, clientId);
|
||||
// Handle unsubscribe command
|
||||
var parseWebSocket = {
|
||||
clientId: 1
|
||||
@@ -274,7 +277,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(subscriptions.size).toBe(0);
|
||||
});
|
||||
|
||||
it('can set connect command message handler for a parseWebSocket', function() {
|
||||
it('can set connect command message handler for a parseWebSocket', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Register mock connect/subscribe/unsubscribe handler for the server
|
||||
parseLiveQueryServer._handleConnect = jasmine.createSpy('_handleSubscribe');
|
||||
@@ -358,7 +361,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(args[0]).toBe(parseWebSocket);
|
||||
expect(JSON.stringify(args[1])).toBe(updateRequest);
|
||||
expect(parseLiveQueryServer._handleUnsubscribe).toHaveBeenCalled();
|
||||
let unsubArgs = parseLiveQueryServer._handleUnsubscribe.calls.mostRecent().args;
|
||||
const unsubArgs = parseLiveQueryServer._handleUnsubscribe.calls.mostRecent().args;
|
||||
expect(unsubArgs.length).toBe(3);
|
||||
expect(unsubArgs[2]).toBe(false);
|
||||
expect(parseLiveQueryServer._handleSubscribe).toHaveBeenCalled();
|
||||
@@ -393,6 +396,28 @@ describe('ParseLiveQueryServer', function() {
|
||||
parseWebSocket.emit('disconnect');
|
||||
});
|
||||
|
||||
it('can forward event to cloud code', function() {
|
||||
const cloudCodeHandler = {
|
||||
handler: () => {}
|
||||
}
|
||||
const spy = spyOn(cloudCodeHandler, 'handler').and.callThrough();
|
||||
Parse.Cloud.onLiveQueryEvent(cloudCodeHandler.handler);
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var EventEmitter = require('events');
|
||||
var parseWebSocket = new EventEmitter();
|
||||
parseWebSocket.clientId = 1;
|
||||
// Register message handlers for the parseWebSocket
|
||||
parseLiveQueryServer._onConnect(parseWebSocket);
|
||||
|
||||
// Make sure we do not crash
|
||||
// Trigger disconnect event
|
||||
parseWebSocket.emit('disconnect');
|
||||
expect(spy).toHaveBeenCalled();
|
||||
// call for ws_connect, another for ws_disconnect
|
||||
expect(spy.calls.count()).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
// TODO: Test server can set disconnect command message handler for a parseWebSocket
|
||||
|
||||
it('has no subscription and can handle object delete command', function() {
|
||||
@@ -452,7 +477,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
key: 'value',
|
||||
className: testClassName
|
||||
});
|
||||
// Make mock message
|
||||
// Make mock message
|
||||
var message = {
|
||||
currentParseObject: parseObject
|
||||
};
|
||||
@@ -533,7 +558,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
// In order to mimic a enter, we need original match return false
|
||||
// and the current match return true
|
||||
var counter = 0;
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -567,7 +592,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var requestId = 2;
|
||||
addMockSubscription(parseLiveQueryServer, clientId, requestId);
|
||||
// Mock _matchesSubscription to return matching
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -603,7 +628,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
// In order to mimic a leave, we need original match return true
|
||||
// and the current match return false
|
||||
var counter = 0;
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -637,7 +662,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var requestId = 2;
|
||||
addMockSubscription(parseLiveQueryServer, clientId, requestId);
|
||||
// Mock _matchesSubscription to return matching
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -746,7 +771,6 @@ describe('ParseLiveQueryServer', function() {
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
var isChecked = false;
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
@@ -916,7 +940,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
|
||||
spyOn(Parse, "Query").and.callFake(function(){
|
||||
return {
|
||||
equalTo(relation, value) {
|
||||
equalTo() {
|
||||
// Nothing to do here
|
||||
},
|
||||
find() {
|
||||
@@ -929,7 +953,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
@@ -952,7 +976,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
|
||||
spyOn(Parse, "Query").and.callFake(function(){
|
||||
return {
|
||||
equalTo(relation, value) {
|
||||
equalTo() {
|
||||
// Nothing to do here
|
||||
},
|
||||
find() {
|
||||
@@ -1019,6 +1043,89 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(parseLiveQueryServer._validateKeys(request, parseLiveQueryServer.keyPairs)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can validate client has master key when valid', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
|
||||
keyPairs: {
|
||||
masterKey: 'test'
|
||||
}
|
||||
});
|
||||
var request = {
|
||||
masterKey: 'test'
|
||||
};
|
||||
|
||||
expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can validate client doesn\'t have master key when invalid', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
|
||||
keyPairs: {
|
||||
masterKey: 'test'
|
||||
}
|
||||
});
|
||||
var request = {
|
||||
masterKey: 'notValid'
|
||||
};
|
||||
|
||||
expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('can validate client doesn\'t have master key when not provided', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
|
||||
keyPairs: {
|
||||
masterKey: 'test'
|
||||
}
|
||||
});
|
||||
|
||||
expect(parseLiveQueryServer._hasMasterKey({}, parseLiveQueryServer.keyPairs)).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('can validate client doesn\'t have master key when validKeyPairs is empty', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer({}, {});
|
||||
var request = {
|
||||
masterKey: 'test'
|
||||
};
|
||||
|
||||
expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('will match non-public ACL when client has master key', function(done){
|
||||
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(false);
|
||||
var client = {
|
||||
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
|
||||
}),
|
||||
hasMasterKey: true
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('won\'t match non-public ACL when client has no master key', function(done){
|
||||
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(false);
|
||||
var client = {
|
||||
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
|
||||
}),
|
||||
hasMasterKey: false
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
jasmine.restoreLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer');
|
||||
jasmine.restoreLibrary('../src/LiveQuery/Client', 'Client');
|
||||
|
||||
+80
-85
@@ -14,7 +14,7 @@
|
||||
|
||||
describe('Parse.Object testing', () => {
|
||||
it("create", function(done) {
|
||||
create({ "test" : "test" }, function(model, response) {
|
||||
create({ "test" : "test" }, function(model) {
|
||||
ok(model.id, "Should have an objectId set");
|
||||
equal(model.get("test"), "test", "Should have the right attribute");
|
||||
done();
|
||||
@@ -22,11 +22,11 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("update", function(done) {
|
||||
create({ "test" : "test" }, function(model, response) {
|
||||
create({ "test" : "test" }, function(model) {
|
||||
var t2 = new TestObject({ objectId: model.id });
|
||||
t2.set("test", "changed");
|
||||
t2.save(null, {
|
||||
success: function(model, response) {
|
||||
success: function(model) {
|
||||
equal(model.get("test"), "changed", "Update should have succeeded");
|
||||
done();
|
||||
}
|
||||
@@ -72,10 +72,10 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("get", function(done) {
|
||||
create({ "test" : "test" }, function(model, response) {
|
||||
create({ "test" : "test" }, function(model) {
|
||||
var t2 = new TestObject({ objectId: model.id });
|
||||
t2.fetch({
|
||||
success: function(model2, response) {
|
||||
success: function(model2) {
|
||||
equal(model2.get("test"), "test", "Update should have succeeded");
|
||||
ok(model2.id);
|
||||
equal(model2.id, model.id, "Ids should match");
|
||||
@@ -144,16 +144,16 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("save adds no data keys (other than createdAt and updatedAt)",
|
||||
function(done) {
|
||||
var object = new TestObject();
|
||||
object.save(null, {
|
||||
success: function() {
|
||||
var keys = Object.keys(object.attributes).sort();
|
||||
equal(keys.length, 2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
function(done) {
|
||||
var object = new TestObject();
|
||||
object.save(null, {
|
||||
success: function() {
|
||||
var keys = Object.keys(object.attributes).sort();
|
||||
equal(keys.length, 2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("recursive save", function(done) {
|
||||
var item = new Item();
|
||||
@@ -269,20 +269,19 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])("can set null", function(done) {
|
||||
var errored = false;
|
||||
var obj = new Parse.Object("TestObject");
|
||||
obj.set("foo", null);
|
||||
obj.save(null, {
|
||||
success: function(obj) {
|
||||
on_db('mongo', () => {
|
||||
on_db('mongo', () => {
|
||||
equal(obj.get("foo"), null);
|
||||
});
|
||||
on_db('postgres', () => {
|
||||
on_db('postgres', () => {
|
||||
fail('should not succeed');
|
||||
});
|
||||
done();
|
||||
},
|
||||
error: function(obj, error) {
|
||||
error: function() {
|
||||
fail('should not fail');
|
||||
done();
|
||||
}
|
||||
@@ -323,11 +322,11 @@ describe('Parse.Object testing', () => {
|
||||
it("invalid class name", function(done) {
|
||||
var item = new Parse.Object("Foo^bar");
|
||||
item.save(null, {
|
||||
success: function(item) {
|
||||
success: function() {
|
||||
ok(false, "The name should have been invalid.");
|
||||
done();
|
||||
},
|
||||
error: function(item, error) {
|
||||
error: function() {
|
||||
// Because the class name is invalid, the router will not be able to route
|
||||
// it, so it will actually return a -1 error code.
|
||||
// equal(error.code, Parse.Error.INVALID_CLASS_NAME);
|
||||
@@ -339,14 +338,13 @@ describe('Parse.Object testing', () => {
|
||||
it("invalid key name", function(done) {
|
||||
var item = new Parse.Object("Item");
|
||||
ok(!item.set({"foo^bar": "baz"}),
|
||||
'Item should not be updated with invalid key.');
|
||||
'Item should not be updated with invalid key.');
|
||||
item.save({ "foo^bar": "baz" }).then(fail, done);
|
||||
});
|
||||
|
||||
it("invalid __type", function(done) {
|
||||
var item = new Parse.Object("Item");
|
||||
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes'];
|
||||
var Error = Parse.Error;
|
||||
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes', 'Polygon'];
|
||||
var tests = types.map(type => {
|
||||
var test = new Parse.Object("Item");
|
||||
test.set('foo', {
|
||||
@@ -368,7 +366,7 @@ describe('Parse.Object testing', () => {
|
||||
"foo": {
|
||||
__type: "IvalidName"
|
||||
}
|
||||
}).then(fail, err => next(0));
|
||||
}).then(fail, () => next(0));
|
||||
});
|
||||
|
||||
it("simple field deletion", function(done) {
|
||||
@@ -598,8 +596,8 @@ describe('Parse.Object testing', () => {
|
||||
var query = new Parse.Query('X');
|
||||
return query.get(x1.id);
|
||||
}).then((x3) => {
|
||||
let stuff = x3.get('stuff');
|
||||
let expected = [1, 2, 4];
|
||||
const stuff = x3.get('stuff');
|
||||
const expected = [1, 2, 4];
|
||||
expect(stuff.length).toBe(expected.length);
|
||||
for (var i of stuff) {
|
||||
expect(expected.indexOf(i) >= 0).toBe(true);
|
||||
@@ -609,7 +607,7 @@ describe('Parse.Object testing', () => {
|
||||
on_db('mongo', () => {
|
||||
jfail(error);
|
||||
});
|
||||
on_db('postgres', () => {
|
||||
on_db('postgres', () => {
|
||||
expect(error.message).toEqual("Postgres does not support AddUnique operator.");
|
||||
});
|
||||
done();
|
||||
@@ -630,12 +628,12 @@ describe('Parse.Object testing', () => {
|
||||
var query = new Parse.Query('X');
|
||||
return query.get(x1.id);
|
||||
}).then((x3) => {
|
||||
let stuff = x3.get('stuff');
|
||||
let target = [1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}];
|
||||
const stuff = x3.get('stuff');
|
||||
const target = [1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}];
|
||||
expect(stuff.length).toEqual(target.length);
|
||||
let found = 0;
|
||||
for (let thing in target) {
|
||||
for (let st in stuff) {
|
||||
for (const thing in target) {
|
||||
for (const st in stuff) {
|
||||
if (st == thing) {
|
||||
found++;
|
||||
}
|
||||
@@ -688,7 +686,7 @@ describe('Parse.Object testing', () => {
|
||||
|
||||
done();
|
||||
},
|
||||
error: function(object, error) {
|
||||
error: function() {
|
||||
ok(false, "This should have saved.");
|
||||
done();
|
||||
}
|
||||
@@ -1079,7 +1077,7 @@ describe('Parse.Object testing', () => {
|
||||
parent.set('children', [child1, child2]);
|
||||
|
||||
parent.save(null, {
|
||||
success: function(parent) {
|
||||
success: function() {
|
||||
var query = new Parse.Query(Child);
|
||||
query.ascending('name');
|
||||
query.find({
|
||||
@@ -1199,8 +1197,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("toJSON saved object", function(done) {
|
||||
var _ = Parse._;
|
||||
create({ "foo" : "bar" }, function(model, response) {
|
||||
create({ "foo" : "bar" }, function(model) {
|
||||
var objJSON = model.toJSON();
|
||||
ok(objJSON.foo, "expected json to contain key 'foo'");
|
||||
ok(objJSON.objectId, "expected json to contain key 'objectId'");
|
||||
@@ -1274,7 +1271,7 @@ describe('Parse.Object testing', () => {
|
||||
return bryan.save({
|
||||
meal: "tomatoes"
|
||||
});
|
||||
}, function(error) {
|
||||
}, function() {
|
||||
ok(false, "Save should have succeeded.");
|
||||
}).then(function() {
|
||||
ok(false, "Save should have failed.");
|
||||
@@ -1331,7 +1328,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])("bytes work", function(done) {
|
||||
it("bytes work", function(done) {
|
||||
Parse.Promise.as().then(function() {
|
||||
var obj = new TestObject();
|
||||
obj.set("bytes", { __type: "Bytes", base64: "ZnJveW8=" });
|
||||
@@ -1393,7 +1390,7 @@ describe('Parse.Object testing', () => {
|
||||
}
|
||||
equal(itemsAgain.length, numItems, "Should get the array back");
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
var newValue = i*2;
|
||||
var newValue = i * 2;
|
||||
item.set("x", newValue);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
@@ -1401,9 +1398,9 @@ describe('Parse.Object testing', () => {
|
||||
return Parse.Object.fetchAll(items);
|
||||
}).then(function(fetchedItemsAgain) {
|
||||
equal(fetchedItemsAgain.length, numItems,
|
||||
"Number of items fetched should not change");
|
||||
"Number of items fetched should not change");
|
||||
fetchedItemsAgain.forEach(function(item, i) {
|
||||
equal(item.get("x"), i*2);
|
||||
equal(item.get("x"), i * 2);
|
||||
});
|
||||
done();
|
||||
});
|
||||
@@ -1460,7 +1457,7 @@ describe('Parse.Object testing', () => {
|
||||
}
|
||||
equal(itemsAgain.length, numItems, "Should get the array back");
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
var newValue = i*2;
|
||||
var newValue = i * 2;
|
||||
item.set("x", newValue);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
@@ -1468,13 +1465,13 @@ describe('Parse.Object testing', () => {
|
||||
return Parse.Object.fetchAll(items, {
|
||||
success: function(fetchedItemsAgain) {
|
||||
equal(fetchedItemsAgain.length, numItems,
|
||||
"Number of items fetched should not change");
|
||||
"Number of items fetched should not change");
|
||||
fetchedItemsAgain.forEach(function(item, i) {
|
||||
equal(item.get("x"), i*2);
|
||||
equal(item.get("x"), i * 2);
|
||||
});
|
||||
done();
|
||||
},
|
||||
error: function(error) {
|
||||
error: function() {
|
||||
ok(false, "Failed to fetchAll");
|
||||
done();
|
||||
}
|
||||
@@ -1502,13 +1499,11 @@ describe('Parse.Object testing', () => {
|
||||
it("fetchAll error on unsaved object", function(done) {
|
||||
var unsavedObjectArray = [new TestObject()];
|
||||
Parse.Object.fetchAll(unsavedObjectArray,
|
||||
expectError(Parse.Error.MISSING_OBJECT_ID, done));
|
||||
expectError(Parse.Error.MISSING_OBJECT_ID, done));
|
||||
});
|
||||
|
||||
it("fetchAll error on deleted object", function(done) {
|
||||
var numItems = 11;
|
||||
var container = new Container();
|
||||
var subContainer = new Container();
|
||||
var items = [];
|
||||
for (var i = 0; i < numItems; i++) {
|
||||
var item = new Item();
|
||||
@@ -1586,14 +1581,14 @@ describe('Parse.Object testing', () => {
|
||||
return;
|
||||
}
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
item.set("x", i*2);
|
||||
item.set("x", i * 2);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
}).then(function() {
|
||||
return Parse.Object.fetchAllIfNeeded(items);
|
||||
}).then(function(fetchedItems) {
|
||||
equal(fetchedItems.length, numItems,
|
||||
"Number of items should not change");
|
||||
"Number of items should not change");
|
||||
fetchedItems.forEach(function(item, i) {
|
||||
equal(item.get("x"), i);
|
||||
});
|
||||
@@ -1624,7 +1619,7 @@ describe('Parse.Object testing', () => {
|
||||
return;
|
||||
}
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
item.set("x", i*2);
|
||||
item.set("x", i * 2);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
}).then(function() {
|
||||
@@ -1632,14 +1627,14 @@ describe('Parse.Object testing', () => {
|
||||
return Parse.Object.fetchAllIfNeeded(items, {
|
||||
success: function(fetchedItems) {
|
||||
equal(fetchedItems.length, numItems,
|
||||
"Number of items should not change");
|
||||
"Number of items should not change");
|
||||
fetchedItems.forEach(function(item, j) {
|
||||
equal(item.get("x"), j);
|
||||
});
|
||||
done();
|
||||
},
|
||||
|
||||
error: function(error) {
|
||||
error: function() {
|
||||
ok(false, "Failed to fetchAll");
|
||||
done();
|
||||
}
|
||||
@@ -1685,7 +1680,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
equal(User1.className, "_User",
|
||||
"className is rewritten by default");
|
||||
"className is rewritten by default");
|
||||
|
||||
Parse.User.allowCustomUserClass(true);
|
||||
equal(Parse.CoreManager.get('PERFORM_USER_REWRITE'), false);
|
||||
@@ -1694,7 +1689,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
equal(User2.className, "User",
|
||||
"className is not rewritten when allowCustomUserClass(true)");
|
||||
"className is not rewritten when allowCustomUserClass(true)");
|
||||
|
||||
// Set back to default so as not to break other tests.
|
||||
Parse.User.allowCustomUserClass(false);
|
||||
@@ -1734,7 +1729,7 @@ describe('Parse.Object testing', () => {
|
||||
return t3.fetch();
|
||||
}).then(function(t3) {
|
||||
equal(t3.get("test"), "test",
|
||||
"Fetch should have grabbed server 'test' property.");
|
||||
"Fetch should have grabbed server 'test' property.");
|
||||
done();
|
||||
}, function(error) {
|
||||
ok(false, error);
|
||||
@@ -1763,7 +1758,7 @@ describe('Parse.Object testing', () => {
|
||||
var obj2 = new TestObject();
|
||||
obj2.increment('astring');
|
||||
return obj2.save();
|
||||
}).then((obj2) => {
|
||||
}).then(() => {
|
||||
fail('Should not have saved.');
|
||||
done();
|
||||
}, (error) => {
|
||||
@@ -1847,10 +1842,10 @@ describe('Parse.Object testing', () => {
|
||||
"_nested": "key"
|
||||
}
|
||||
});
|
||||
object.save().then( res => {
|
||||
object.save().then(res => {
|
||||
ok(res);
|
||||
return res.fetch();
|
||||
}).then( res => {
|
||||
}).then(res => {
|
||||
const foo = res.get("foo");
|
||||
expect(foo["_bar"]).toEqual("_");
|
||||
expect(foo["baz_bar"]).toEqual(1);
|
||||
@@ -1858,7 +1853,7 @@ describe('Parse.Object testing', () => {
|
||||
expect(foo["_0"]).toEqual("underscore_zero");
|
||||
expect(foo["_more"]["_nested"]).toEqual("key");
|
||||
done();
|
||||
}).fail( err => {
|
||||
}).fail(err => {
|
||||
jfail(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
@@ -1866,15 +1861,15 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it('should have undefined includes when object is missing', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnObject");
|
||||
const obj1 = new Parse.Object("AnObject");
|
||||
const obj2 = new Parse.Object("AnObject");
|
||||
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
obj1.set("obj", obj2);
|
||||
// Save the pointer, delete the pointee
|
||||
return obj1.save().then(() => { return obj2.destroy() });
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
const query = new Parse.Query("AnObject");
|
||||
query.include("obj");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
@@ -1882,9 +1877,9 @@ describe('Parse.Object testing', () => {
|
||||
if (res[0]) {
|
||||
expect(res[0].get("obj")).toBe(undefined);
|
||||
}
|
||||
let query = new Parse.Query("AnObject");
|
||||
const query = new Parse.Query("AnObject");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
if (res[0]) {
|
||||
expect(res[0].get("obj")).not.toBe(undefined);
|
||||
@@ -1901,16 +1896,16 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it('should have undefined includes when object is missing on deeper path', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnObject");
|
||||
let obj3 = new Parse.Object("AnObject");
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const obj1 = new Parse.Object("AnObject");
|
||||
const obj2 = new Parse.Object("AnObject");
|
||||
const obj3 = new Parse.Object("AnObject");
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
obj1.set("obj", obj2);
|
||||
obj2.set("obj", obj3);
|
||||
// Save the pointer, delete the pointee
|
||||
return Parse.Object.saveAll([obj1, obj2]).then(() => { return obj3.destroy() });
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
const query = new Parse.Query("AnObject");
|
||||
query.include("obj.obj");
|
||||
return query.get(obj1.id);
|
||||
}).then((res) => {
|
||||
@@ -1924,9 +1919,9 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it('should handle includes on null arrays #2752', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnotherObject");
|
||||
let obj3 = new Parse.Object("NestedObject");
|
||||
const obj1 = new Parse.Object("AnObject");
|
||||
const obj2 = new Parse.Object("AnotherObject");
|
||||
const obj3 = new Parse.Object("NestedObject");
|
||||
obj3.set({
|
||||
"foo": "bar"
|
||||
})
|
||||
@@ -1934,17 +1929,17 @@ describe('Parse.Object testing', () => {
|
||||
"key": obj3
|
||||
})
|
||||
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
obj1.set("objects", [null, null, obj2]);
|
||||
return obj1.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
const query = new Parse.Query("AnObject");
|
||||
query.include("objects.key");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
let obj = res[0];
|
||||
const obj = res[0];
|
||||
expect(obj.get("objects")).not.toBe(undefined);
|
||||
let array = obj.get("objects");
|
||||
const array = obj.get("objects");
|
||||
expect(Array.isArray(array)).toBe(true);
|
||||
expect(array[0]).toBe(null);
|
||||
expect(array[1]).toBe(null);
|
||||
@@ -1957,25 +1952,25 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it('should handle select and include #2786', (done) => {
|
||||
let score = new Parse.Object("GameScore");
|
||||
let player = new Parse.Object("Player");
|
||||
const score = new Parse.Object("GameScore");
|
||||
const player = new Parse.Object("Player");
|
||||
score.set({
|
||||
"score": 1234
|
||||
});
|
||||
|
||||
score.save().then(() => {
|
||||
score.save().then(() => {
|
||||
player.set("gameScore", score);
|
||||
player.set("other", "value");
|
||||
return player.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("Player");
|
||||
const query = new Parse.Query("Player");
|
||||
query.include("gameScore");
|
||||
query.select("gameScore");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
let obj = res[0];
|
||||
let gameScore = obj.get("gameScore");
|
||||
let other = obj.get("other");
|
||||
const obj = res[0];
|
||||
const gameScore = obj.get("gameScore");
|
||||
const other = obj.get("other");
|
||||
expect(other).toBeUndefined();
|
||||
expect(gameScore).not.toBeUndefined();
|
||||
expect(gameScore.get("score")).toBe(1234);
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
const TestObject = Parse.Object.extend('TestObject');
|
||||
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const rp = require('request-promise');
|
||||
const defaultHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
}
|
||||
|
||||
describe('Parse.Polygon testing', () => {
|
||||
it('polygon save open path', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const closed = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon.__type, 'Polygon');
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon save closed path', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon.__type, 'Polygon');
|
||||
equal(polygon.coordinates, coords);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon equalTo (open/closed) path', (done) => {
|
||||
const openPoints = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const closedPoints = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
const openPolygon = {__type: 'Polygon', coordinates: openPoints};
|
||||
const closedPolygon = {__type: 'Polygon', coordinates: closedPoints};
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', openPolygon);
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', openPolygon);
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon.__type, 'Polygon');
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', closedPolygon);
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon.__type, 'Polygon');
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon update', (done) => {
|
||||
const oldCoords = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const oldPolygon = {__type: 'Polygon', coordinates: oldCoords};
|
||||
const newCoords = [[2,2],[2,3],[3,3],[3,2]];
|
||||
const newPolygon = {__type: 'Polygon', coordinates: newCoords};
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', oldPolygon);
|
||||
return obj.save().then(() => {
|
||||
obj.set('polygon', newPolygon);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
newCoords.push(newCoords[0]);
|
||||
equal(polygon.__type, 'Polygon');
|
||||
equal(polygon.coordinates, newCoords);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon invalid value', (done) => {
|
||||
const coords = [['foo','bar'],[0,1],[1,0],[1,1],[0,0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then(done.fail, done);
|
||||
});
|
||||
|
||||
it('polygon three points minimum', (done) => {
|
||||
const coords = [[0,0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
obj.save().then(done.fail, done);
|
||||
});
|
||||
|
||||
it('polygon three different points minimum', (done) => {
|
||||
const coords = [[0,0],[0,1],[0,0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
obj.save().then(done.fail, done);
|
||||
});
|
||||
|
||||
it('polygon counterclockwise', (done) => {
|
||||
const coords = [[1,1],[0,1],[0,0],[1,0]];
|
||||
const closed = [[1,1],[0,1],[0,0],[1,0],[1,1]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon.__type, 'Polygon');
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygonContain query', (done) => {
|
||||
const points1 = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const points2 = [[0,0],[0,2],[2,2],[2,0]];
|
||||
const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]];
|
||||
const polygon1 = {__type: 'Polygon', coordinates: points1};
|
||||
const polygon2 = {__type: 'Polygon', coordinates: points2};
|
||||
const polygon3 = {__type: 'Polygon', coordinates: points3};
|
||||
const obj1 = new TestObject({location: polygon1});
|
||||
const obj2 = new TestObject({location: polygon2});
|
||||
const obj3 = new TestObject({location: polygon3});
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 }
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygonContain invalid input', (done) => {
|
||||
const points = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const polygon = {__type: 'Polygon', coordinates: points};
|
||||
const obj = new TestObject({location: polygon});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 }
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then(done.fail, done);
|
||||
});
|
||||
|
||||
it('polygonContain invalid geoPoint', (done) => {
|
||||
const points = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const polygon = {__type: 'Polygon', coordinates: points};
|
||||
const obj = new TestObject({location: polygon});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: []
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then(done.fail, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('mongo')('Parse.Polygon testing', () => {
|
||||
it('support 2d and 2dsphere', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
const polygon = {__type: 'Polygon', coordinates: coords};
|
||||
const location = {__type: 'GeoPoint', latitude:10, longitude:10};
|
||||
const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI });
|
||||
return reconfigureServer({
|
||||
appId: 'test',
|
||||
restAPIKey: 'rest',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
databaseAdapter
|
||||
}).then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', {location: '2d'});
|
||||
}).then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'});
|
||||
}).then(() => {
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: {
|
||||
'_method': 'POST',
|
||||
location,
|
||||
polygon,
|
||||
polygon2: polygon
|
||||
},
|
||||
headers: defaultHeaders
|
||||
});
|
||||
}).then((resp) => {
|
||||
return rp.post({
|
||||
url: `http://localhost:8378/1/classes/TestObject/${resp.objectId}`,
|
||||
json: {'_method': 'GET'},
|
||||
headers: defaultHeaders
|
||||
});
|
||||
}).then((resp) => {
|
||||
equal(resp.location, location);
|
||||
equal(resp.polygon, polygon);
|
||||
equal(resp.polygon2, polygon);
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
}).then((indexes) => {
|
||||
equal(indexes.length, 4);
|
||||
equal(indexes[0].key, {_id: 1});
|
||||
equal(indexes[1].key, {location: '2d'});
|
||||
equal(indexes[2].key, {polygon: '2dsphere'});
|
||||
equal(indexes[3].key, {polygon2: '2dsphere'});
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon loop is not valid', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,0],[1,1]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
obj.save().then(done.fail, done);
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,7 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create redis publisher', function() {
|
||||
var publisher = ParsePubSub.createPublisher({
|
||||
ParsePubSub.createPublisher({
|
||||
redisURL: 'redisURL'
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create event emitter publisher', function() {
|
||||
var publisher = ParsePubSub.createPublisher({});
|
||||
ParsePubSub.createPublisher({});
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
@@ -39,7 +39,7 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create redis subscriber', function() {
|
||||
var subscriber = ParsePubSub.createSubscriber({
|
||||
ParsePubSub.createSubscriber({
|
||||
redisURL: 'redisURL'
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create event emitter subscriber', function() {
|
||||
var subscriptionInfos = ParsePubSub.createSubscriber({});
|
||||
ParsePubSub.createSubscriber({});
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
@@ -59,7 +59,7 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create publisher/sub with custom adapter', function() {
|
||||
let adapter = {
|
||||
const adapter = {
|
||||
createPublisher: jasmine.createSpy('createPublisher'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriber')
|
||||
}
|
||||
@@ -82,7 +82,7 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create publisher/sub with custom function adapter', function() {
|
||||
let adapter = {
|
||||
const adapter = {
|
||||
createPublisher: jasmine.createSpy('createPublisher'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriber')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
'use strict';
|
||||
|
||||
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
const Parse = require('parse/node');
|
||||
const rp = require('request-promise');
|
||||
let databaseAdapter;
|
||||
|
||||
const fullTextHelper = () => {
|
||||
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
||||
if (!databaseAdapter) {
|
||||
databaseAdapter = new PostgresStorageAdapter({ uri: postgresURI });
|
||||
}
|
||||
} else {
|
||||
databaseAdapter = new MongoStorageAdapter({ uri: mongoURI });
|
||||
}
|
||||
const subjects = [
|
||||
'coffee',
|
||||
'Coffee Shopping',
|
||||
'Baking a cake',
|
||||
'baking',
|
||||
'Café Con Leche',
|
||||
'Сырники',
|
||||
'coffee and cream',
|
||||
'Cafe con Leche',
|
||||
];
|
||||
const requests = [];
|
||||
for (const i in subjects) {
|
||||
const request = {
|
||||
method: "POST",
|
||||
body: {
|
||||
subject: subjects[i]
|
||||
},
|
||||
path: "/1/classes/TestObject"
|
||||
};
|
||||
requests.push(request);
|
||||
}
|
||||
return reconfigureServer({
|
||||
appId: 'test',
|
||||
restAPIKey: 'test',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
databaseAdapter
|
||||
}).then(() => {
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/batch',
|
||||
body: {
|
||||
requests
|
||||
},
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('Parse.Query Full Text Search testing', () => {
|
||||
it('fullTextSearch: $search', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(3);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $search, sort', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const order = '$score';
|
||||
const keys = '$score';
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, order, keys, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(3);
|
||||
expect(resp.results[0].score);
|
||||
expect(resp.results[1].score);
|
||||
expect(resp.results[2].score);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $language', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'leche',
|
||||
$language: 'spanish'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $search, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: true
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $language, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'leche',
|
||||
$language: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('mongo')('Parse.Query Full Text Search testing', () => {
|
||||
it('fullTextSearch: $search, only one text index', (done) => {
|
||||
return reconfigureServer({
|
||||
appId: 'test',
|
||||
restAPIKey: 'test',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
databaseAdapter: new MongoStorageAdapter({ uri: mongoURI })
|
||||
}).then(() => {
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/batch',
|
||||
body: {
|
||||
requests: [
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
subject: "coffee is java"
|
||||
},
|
||||
path: "/1/classes/TestObject"
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
subject: "java is coffee"
|
||||
},
|
||||
path: "/1/classes/TestObject"
|
||||
}
|
||||
]
|
||||
},
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', {random: 'text'});
|
||||
}).then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`Should not be more than one text index: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive - false', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('postgres')('Parse.Query Full Text Search testing', () => {
|
||||
it('fullTextSearch: $diacriticSensitive - false', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`$diacriticSensitive - false should not supported: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`$caseSensitive should not supported: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
+665
-340
File diff suppressed because it is too large
Load Diff
+210
-177
@@ -22,11 +22,11 @@ describe('Parse.Relation testing', () => {
|
||||
return relation.query().find();
|
||||
}).then((list) => {
|
||||
equal(list.length, 1,
|
||||
"Should have gotten one element back");
|
||||
"Should have gotten one element back");
|
||||
equal(list[0].id, child.id,
|
||||
"Should have gotten the right value");
|
||||
"Should have gotten the right value");
|
||||
ok(!parent.dirty("child"),
|
||||
"The relation should not be dirty");
|
||||
"The relation should not be dirty");
|
||||
|
||||
relation.remove(child);
|
||||
return parent.save();
|
||||
@@ -34,9 +34,9 @@ describe('Parse.Relation testing', () => {
|
||||
return relation.query().find();
|
||||
}).then((list) => {
|
||||
equal(list.length, 0,
|
||||
"Delete should have worked");
|
||||
"Delete should have worked");
|
||||
ok(!parent.dirty("child"),
|
||||
"The relation should not be dirty");
|
||||
"The relation should not be dirty");
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -46,10 +46,10 @@ describe('Parse.Relation testing', () => {
|
||||
var childObjects = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
childObjects.push(new ChildObject({x:i}));
|
||||
};
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects, expectSuccess({
|
||||
success: function(list) {
|
||||
success: function() {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
@@ -63,9 +63,9 @@ describe('Parse.Relation testing', () => {
|
||||
relation.query().find(expectSuccess({
|
||||
success: function(list) {
|
||||
equal(list.length, 1,
|
||||
"Should have gotten one element back");
|
||||
"Should have gotten one element back");
|
||||
equal(list[0].id, childObjects[0].id,
|
||||
"Should have gotten the right value");
|
||||
"Should have gotten the right value");
|
||||
done();
|
||||
}
|
||||
}));
|
||||
@@ -84,7 +84,7 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects, {
|
||||
success: function(list) {
|
||||
success: function() {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
@@ -99,14 +99,14 @@ describe('Parse.Relation testing', () => {
|
||||
relationAgain.query().find({
|
||||
success: function(list) {
|
||||
equal(list.length, 1,
|
||||
"Should have gotten one element back");
|
||||
"Should have gotten one element back");
|
||||
equal(list[0].id, childObjects[0].id,
|
||||
"Should have gotten the right value");
|
||||
"Should have gotten the right value");
|
||||
ok(!parent.dirty("child"),
|
||||
"The relation should not be dirty");
|
||||
"The relation should not be dirty");
|
||||
done();
|
||||
},
|
||||
error: function(list) {
|
||||
error: function() {
|
||||
ok(false, "This shouldn't have failed");
|
||||
done();
|
||||
}
|
||||
@@ -131,7 +131,7 @@ describe('Parse.Relation testing', () => {
|
||||
var parent;
|
||||
var relation;
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(function(list) {
|
||||
Parse.Object.saveAll(childObjects).then(function() {
|
||||
var ParentObject = Parse.Object.extend('ParentObject');
|
||||
parent = new ParentObject();
|
||||
parent.set('x', 4);
|
||||
@@ -188,11 +188,11 @@ describe('Parse.Relation testing', () => {
|
||||
query.find({
|
||||
success: function(list) {
|
||||
equal(list.length, 1,
|
||||
"There should only be one element");
|
||||
"There should only be one element");
|
||||
ok(list[0] instanceof ChildObject,
|
||||
"Should be of type ChildObject");
|
||||
"Should be of type ChildObject");
|
||||
equal(list[0].id, childObjects[2].id,
|
||||
"We should have gotten back the right result");
|
||||
"We should have gotten back the right result");
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -238,7 +238,7 @@ describe('Parse.Relation testing', () => {
|
||||
success: function(list) {
|
||||
equal(list.length, 1, "There should be only one result");
|
||||
equal(list[0].id, parent2.id,
|
||||
"Should have gotten back the right result");
|
||||
"Should have gotten back the right result");
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -249,35 +249,35 @@ describe('Parse.Relation testing', () => {
|
||||
});
|
||||
|
||||
it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
|
||||
let ChildObject = Parse.Object.extend("ChildObject");
|
||||
let childObjects = [];
|
||||
const ChildObject = Parse.Object.extend("ChildObject");
|
||||
const childObjects = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
childObjects.push(new ChildObject({x: i}));
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
let ParentObject = Parse.Object.extend("ParentObject");
|
||||
let parent = new ParentObject();
|
||||
const ParentObject = Parse.Object.extend("ParentObject");
|
||||
const parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
let parent1Children = parent.relation("child");
|
||||
const parent1Children = parent.relation("child");
|
||||
parent1Children.add(childObjects[0]);
|
||||
parent1Children.add(childObjects[1]);
|
||||
parent1Children.add(childObjects[2]);
|
||||
let parent2 = new ParentObject();
|
||||
const parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
let parent2Children = parent2.relation("child");
|
||||
const parent2Children = parent2.relation("child");
|
||||
parent2Children.add(childObjects[4]);
|
||||
parent2Children.add(childObjects[5]);
|
||||
parent2Children.add(childObjects[6]);
|
||||
|
||||
let parent2OtherChildren = parent2.relation("otherChild");
|
||||
const parent2OtherChildren = parent2.relation("otherChild");
|
||||
parent2OtherChildren.add(childObjects[0]);
|
||||
parent2OtherChildren.add(childObjects[1]);
|
||||
parent2OtherChildren.add(childObjects[2]);
|
||||
|
||||
return Parse.Object.saveAll([parent, parent2]);
|
||||
}).then(() => {
|
||||
let objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
|
||||
const objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
|
||||
objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
|
||||
objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
|
||||
return objectsWithChild0InBothChildren.find();
|
||||
@@ -285,7 +285,7 @@ describe('Parse.Relation testing', () => {
|
||||
//No parent has child 0 in both it's "child" and "otherChild" field;
|
||||
expect(objectsWithChild0InBothChildren.length).toEqual(0);
|
||||
}).then(() => {
|
||||
let objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
|
||||
const objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
|
||||
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
|
||||
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
|
||||
return objectsWithChild4andOtherChild1.find();
|
||||
@@ -304,34 +304,34 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent.id);
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent.id);
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
}).catch(err => {
|
||||
});
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
@@ -345,35 +345,35 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.relation("toChilds").add(childObjects[2]);
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.relation("toChilds").add(childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent2.id);
|
||||
// childObjects[2] is in 2 relations
|
||||
// before the fix, that woul yield 2 results
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent2.id);
|
||||
// childObjects[2] is in 2 relations
|
||||
// before the fix, that woul yield 2 results
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -385,39 +385,39 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query1 = new Parse.Query(ParentObject);
|
||||
query1.containedIn("toChilds", [childObjects[2]]);
|
||||
var query2 = new Parse.Query(ParentObject);
|
||||
query2.equalTo("toChild", childObjects[2]);
|
||||
var query = Parse.Query.or(query1, query2);
|
||||
return query.find().then((list) => {
|
||||
var objectIds = list.map(function(item){
|
||||
return item.id;
|
||||
});
|
||||
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
|
||||
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
|
||||
equal(list.length, 2, "There should be 2 results");
|
||||
done();
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query1 = new Parse.Query(ParentObject);
|
||||
query1.containedIn("toChilds", [childObjects[2]]);
|
||||
var query2 = new Parse.Query(ParentObject);
|
||||
query2.equalTo("toChild", childObjects[2]);
|
||||
var query = Parse.Query.or(query1, query2);
|
||||
return query.find().then((list) => {
|
||||
var objectIds = list.map(function(item){
|
||||
return item.id;
|
||||
});
|
||||
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
|
||||
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
|
||||
equal(list.length, 2, "There should be 2 results");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -510,24 +510,24 @@ describe('Parse.Relation testing', () => {
|
||||
});
|
||||
|
||||
it('should properly get related objects with unfetched queries', (done) => {
|
||||
let objects = [];
|
||||
let owners = [];
|
||||
let allObjects = [];
|
||||
const objects = [];
|
||||
const owners = [];
|
||||
const allObjects = [];
|
||||
// Build 10 Objects and 10 owners
|
||||
while (objects.length != 10) {
|
||||
let object = new Parse.Object('AnObject');
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set({
|
||||
index: objects.length,
|
||||
even: objects.length % 2 == 0
|
||||
});
|
||||
objects.push(object);
|
||||
let owner = new Parse.Object('AnOwner');
|
||||
const owner = new Parse.Object('AnOwner');
|
||||
owners.push(owner);
|
||||
allObjects.push(object);
|
||||
allObjects.push(owner);
|
||||
}
|
||||
|
||||
let anotherOwner = new Parse.Object('AnotherOwner');
|
||||
const anotherOwner = new Parse.Object('AnotherOwner');
|
||||
|
||||
return Parse.Object.saveAll(allObjects.concat([anotherOwner])).then(() => {
|
||||
// put all the AnObject into the anotherOwner relationKey
|
||||
@@ -539,42 +539,45 @@ describe('Parse.Relation testing', () => {
|
||||
return Parse.Object.saveAll(owners.concat([anotherOwner]));
|
||||
}).then(() => {
|
||||
// Query on the relation of another owner
|
||||
let object = new Parse.Object('AnotherOwner');
|
||||
const object = new Parse.Object('AnotherOwner');
|
||||
object.id = anotherOwner.id;
|
||||
let relationQuery = object.relation('relationKey').query();
|
||||
const relationQuery = object.relation('relationKey').query();
|
||||
// Just get the even ones
|
||||
relationQuery.equalTo('even', true);
|
||||
// Make the query on anOwner
|
||||
let query = new Parse.Query('AnOwner');
|
||||
const query = new Parse.Query('AnOwner');
|
||||
// where key match the relation query.
|
||||
query.matchesQuery('key', relationQuery);
|
||||
query.include('key');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(5);
|
||||
results.forEach((result) => {
|
||||
results.forEach((result) => {
|
||||
expect(result.get('key').get('even')).toBe(true);
|
||||
});
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// Query on the relation of another owner
|
||||
let object = new Parse.Object('AnotherOwner');
|
||||
const object = new Parse.Object('AnotherOwner');
|
||||
object.id = anotherOwner.id;
|
||||
let relationQuery = object.relation('relationKey').query();
|
||||
const relationQuery = object.relation('relationKey').query();
|
||||
// Just get the even ones
|
||||
relationQuery.equalTo('even', true);
|
||||
// Make the query on anOwner
|
||||
let query = new Parse.Query('AnOwner');
|
||||
const query = new Parse.Query('AnOwner');
|
||||
// where key match the relation query.
|
||||
query.doesNotMatchQuery('key', relationQuery);
|
||||
query.include('key');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(5);
|
||||
results.forEach((result) => {
|
||||
results.forEach((result) => {
|
||||
expect(result.get('key').get('even')).toBe(false);
|
||||
});
|
||||
done();
|
||||
}, (e) => {
|
||||
fail(JSON.stringify(e));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
@@ -586,21 +589,19 @@ describe('Parse.Relation testing', () => {
|
||||
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
|
||||
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
|
||||
];
|
||||
let persons = [
|
||||
const persons = [
|
||||
new PersonObject({ name: "Bob", hometown: "Djibouti" }),
|
||||
new PersonObject({ name: "Tom", hometown: "Ouagadougou" }),
|
||||
new PersonObject({ name: "Billy", hometown: "Detroit" }),
|
||||
];
|
||||
let owner = new OwnerObject({name: 'Joe'});
|
||||
let ownerId;
|
||||
let allObjects = [owner].concat(restaurants).concat(persons);
|
||||
const owner = new OwnerObject({name: 'Joe'});
|
||||
const allObjects = [owner].concat(restaurants).concat(persons);
|
||||
expect(allObjects.length).toEqual(6);
|
||||
Parse.Object.saveAll([owner].concat(restaurants).concat(persons)).then(function() {
|
||||
ownerId = owner.id;
|
||||
owner.relation('restaurants').add(restaurants);
|
||||
return owner.save()
|
||||
}).then(() => {
|
||||
let unfetchedOwner = new OwnerObject();
|
||||
const unfetchedOwner = new OwnerObject();
|
||||
unfetchedOwner.id = owner.id;
|
||||
var query = unfetchedOwner.relation('restaurants').query();
|
||||
query.greaterThan("ratings", 4);
|
||||
@@ -615,6 +616,9 @@ describe('Parse.Relation testing', () => {
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, (e) => {
|
||||
fail(JSON.stringify(e));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -626,21 +630,19 @@ describe('Parse.Relation testing', () => {
|
||||
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
|
||||
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
|
||||
];
|
||||
let persons = [
|
||||
const persons = [
|
||||
new PersonObject({ name: "Bob", hometown: "Djibouti" }),
|
||||
new PersonObject({ name: "Tom", hometown: "Ouagadougou" }),
|
||||
new PersonObject({ name: "Billy", hometown: "Detroit" }),
|
||||
];
|
||||
let owner = new OwnerObject({name: 'Joe'});
|
||||
let ownerId;
|
||||
let allObjects = [owner].concat(restaurants).concat(persons);
|
||||
const owner = new OwnerObject({name: 'Joe'});
|
||||
const allObjects = [owner].concat(restaurants).concat(persons);
|
||||
expect(allObjects.length).toEqual(6);
|
||||
Parse.Object.saveAll([owner].concat(restaurants).concat(persons)).then(function() {
|
||||
ownerId = owner.id;
|
||||
owner.relation('restaurants').add(restaurants);
|
||||
return owner.save()
|
||||
}).then(() => {
|
||||
let unfetchedOwner = new OwnerObject();
|
||||
const unfetchedOwner = new OwnerObject();
|
||||
unfetchedOwner.id = owner.id;
|
||||
var query = unfetchedOwner.relation('restaurants').query();
|
||||
query.greaterThan("ratings", 4);
|
||||
@@ -657,25 +659,28 @@ describe('Parse.Relation testing', () => {
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, (e) => {
|
||||
fail(JSON.stringify(e));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('relations are not bidirectional (regression test for #871)', done => {
|
||||
let PersonObject = Parse.Object.extend("Person");
|
||||
let p1 = new PersonObject();
|
||||
let p2 = new PersonObject();
|
||||
const PersonObject = Parse.Object.extend("Person");
|
||||
const p1 = new PersonObject();
|
||||
const p2 = new PersonObject();
|
||||
Parse.Object.saveAll([p1, p2]).then(results => {
|
||||
let p1 = results[0];
|
||||
let p2 = results[1];
|
||||
let relation = p1.relation('relation');
|
||||
const p1 = results[0];
|
||||
const p2 = results[1];
|
||||
const relation = p1.relation('relation');
|
||||
relation.add(p2);
|
||||
p1.save().then(() => {
|
||||
let query = new Parse.Query(PersonObject);
|
||||
const query = new Parse.Query(PersonObject);
|
||||
query.equalTo('relation', p1);
|
||||
query.find().then(results => {
|
||||
expect(results.length).toEqual(0);
|
||||
|
||||
let query = new Parse.Query(PersonObject);
|
||||
const query = new Parse.Query(PersonObject);
|
||||
query.equalTo('relation', p2);
|
||||
query.find().then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -689,56 +694,84 @@ describe('Parse.Relation testing', () => {
|
||||
|
||||
it('can query roles in Cloud Code (regession test #1489)', done => {
|
||||
Parse.Cloud.define('isAdmin', (request, response) => {
|
||||
let query = new Parse.Query(Parse.Role);
|
||||
const query = new Parse.Query(Parse.Role);
|
||||
query.equalTo('name', 'admin');
|
||||
query.first({ useMasterKey: true })
|
||||
.then(role => {
|
||||
let relation = new Parse.Relation(role, 'users');
|
||||
let admins = relation.query();
|
||||
admins.equalTo('username', request.user.get('username'));
|
||||
admins.first({ useMasterKey: true })
|
||||
.then(user => {
|
||||
if (user) {
|
||||
response.success(user);
|
||||
done();
|
||||
} else {
|
||||
fail('Should have found admin user, found nothing instead');
|
||||
done();
|
||||
}
|
||||
.then(role => {
|
||||
const relation = new Parse.Relation(role, 'users');
|
||||
const admins = relation.query();
|
||||
admins.equalTo('username', request.user.get('username'));
|
||||
admins.first({ useMasterKey: true })
|
||||
.then(user => {
|
||||
if (user) {
|
||||
response.success(user);
|
||||
done();
|
||||
} else {
|
||||
fail('Should have found admin user, found nothing instead');
|
||||
done();
|
||||
}
|
||||
}, () => {
|
||||
fail('User not admin');
|
||||
done();
|
||||
})
|
||||
}, error => {
|
||||
fail('User not admin');
|
||||
fail('Should have found admin user, errored instead');
|
||||
fail(error);
|
||||
done();
|
||||
})
|
||||
}, error => {
|
||||
fail('Should have found admin user, errored instead');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let adminUser = new Parse.User();
|
||||
const adminUser = new Parse.User();
|
||||
adminUser.set('username', 'name');
|
||||
adminUser.set('password', 'pass');
|
||||
adminUser.signUp()
|
||||
.then(adminUser => {
|
||||
let adminACL = new Parse.ACL();
|
||||
adminACL.setPublicReadAccess(true);
|
||||
.then(adminUser => {
|
||||
const adminACL = new Parse.ACL();
|
||||
adminACL.setPublicReadAccess(true);
|
||||
|
||||
// Create admin role
|
||||
let adminRole = new Parse.Role('admin', adminACL);
|
||||
adminRole.getUsers().add(adminUser);
|
||||
adminRole.save()
|
||||
.then(() => {
|
||||
Parse.Cloud.run('isAdmin');
|
||||
// Create admin role
|
||||
const adminRole = new Parse.Role('admin', adminACL);
|
||||
adminRole.getUsers().add(adminUser);
|
||||
adminRole.save()
|
||||
.then(() => {
|
||||
Parse.Cloud.run('isAdmin');
|
||||
}, error => {
|
||||
fail('failed to save role');
|
||||
fail(error);
|
||||
done()
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to save role');
|
||||
fail('failed to sign up');
|
||||
fail(error);
|
||||
done()
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be saved without error', done => {
|
||||
const obj1 = new Parse.Object('PPAP');
|
||||
obj1.save()
|
||||
.then(() => {
|
||||
const newRelation = obj1.relation('aRelation');
|
||||
newRelation.add(obj1);
|
||||
obj1.save().then(() => {
|
||||
const relation = obj1.get('aRelation');
|
||||
obj1.set('aRelation', relation);
|
||||
obj1.save().then(() => {
|
||||
done();
|
||||
}, error => {
|
||||
fail('failed to save ParseRelation object');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to create relation field');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to save obj');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to sign up');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+248
-68
@@ -13,7 +13,7 @@ describe('Parse Role testing', () => {
|
||||
|
||||
createTestUser().then((x) => {
|
||||
user = x;
|
||||
let acl = new Parse.ACL();
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(false);
|
||||
role = new Parse.Object('_Role');
|
||||
@@ -22,7 +22,7 @@ describe('Parse Role testing', () => {
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
return role.save({}, { useMasterKey: true });
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('_Role');
|
||||
return query.find({ useMasterKey: true });
|
||||
}).then((x) => {
|
||||
@@ -42,7 +42,7 @@ describe('Parse Role testing', () => {
|
||||
acl.setRoleWriteAccess('Foos', true);
|
||||
obj.setACL(acl);
|
||||
return obj.save();
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('TestObject');
|
||||
return query.find({ sessionToken: user.getSessionToken() });
|
||||
}).then((x) => {
|
||||
@@ -55,7 +55,7 @@ describe('Parse Role testing', () => {
|
||||
x.set('foo', 'baz');
|
||||
// This should fail:
|
||||
return x.save({},{sessionToken: ""});
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
fail('Should not have been able to save.');
|
||||
}, (e) => {
|
||||
expect(e.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
|
||||
@@ -98,10 +98,10 @@ describe('Parse Role testing', () => {
|
||||
var user,
|
||||
auth,
|
||||
getAllRolesSpy;
|
||||
createTestUser().then( (newUser) => {
|
||||
createTestUser().then((newUser) => {
|
||||
user = newUser;
|
||||
return createAllRoles(user);
|
||||
}).then ( (roles) => {
|
||||
}).then ((roles) => {
|
||||
var rootRoleObj = roleObjs[rootRole];
|
||||
roles.forEach(function(role, i) {
|
||||
// Add all roles to the RootRole
|
||||
@@ -115,16 +115,16 @@ describe('Parse Role testing', () => {
|
||||
});
|
||||
|
||||
return Parse.Object.saveAll(roles, { useMasterKey: true });
|
||||
}).then( () => {
|
||||
}).then(() => {
|
||||
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
|
||||
getAllRolesSpy = spyOn(auth, "_getAllRolesNamesForRoleIds").and.callThrough();
|
||||
|
||||
return auth._loadRoles();
|
||||
}).then ( (roles) => {
|
||||
}).then ((roles) => {
|
||||
expect(roles.length).toEqual(4);
|
||||
|
||||
allRoles.forEach(function(name) {
|
||||
expect(roles.indexOf("role:"+name)).not.toBe(-1);
|
||||
expect(roles.indexOf("role:" + name)).not.toBe(-1);
|
||||
});
|
||||
|
||||
// 1 Query for the initial setup
|
||||
@@ -135,7 +135,7 @@ describe('Parse Role testing', () => {
|
||||
// 1 call for the 2nd layer
|
||||
expect(getAllRolesSpy.calls.count()).toEqual(2);
|
||||
done()
|
||||
}).catch( (err) => {
|
||||
}).catch(() => {
|
||||
fail("should succeed");
|
||||
done();
|
||||
});
|
||||
@@ -145,70 +145,88 @@ describe('Parse Role testing', () => {
|
||||
it("should recursively load roles", (done) => {
|
||||
var rolesNames = ["FooRole", "BarRole", "BazRole"];
|
||||
var roleIds = {};
|
||||
createTestUser().then( (user) => {
|
||||
// Put the user on the 1st role
|
||||
return createRole(rolesNames[0], null, user).then( (aRole) => {
|
||||
roleIds[aRole.get("name")] = aRole.id;
|
||||
// set the 1st role as a sibling of the second
|
||||
// user will should have 2 role now
|
||||
return createRole(rolesNames[1], aRole, null);
|
||||
}).then( (anotherRole) => {
|
||||
roleIds[anotherRole.get("name")] = anotherRole.id;
|
||||
// set this role as a sibling of the last
|
||||
// the user should now have 3 roles
|
||||
return createRole(rolesNames[2], anotherRole, null);
|
||||
}).then( (lastRole) => {
|
||||
roleIds[lastRole.get("name")] = lastRole.id;
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
|
||||
return auth._loadRoles();
|
||||
})
|
||||
}).then( (roles) => {
|
||||
expect(roles.length).toEqual(3);
|
||||
rolesNames.forEach( (name) => {
|
||||
expect(roles.indexOf('role:'+name)).not.toBe(-1);
|
||||
});
|
||||
done();
|
||||
}, function(err){
|
||||
fail("should succeed")
|
||||
done();
|
||||
});
|
||||
createTestUser().then((user) => {
|
||||
// Put the user on the 1st role
|
||||
return createRole(rolesNames[0], null, user).then((aRole) => {
|
||||
roleIds[aRole.get("name")] = aRole.id;
|
||||
// set the 1st role as a sibling of the second
|
||||
// user will should have 2 role now
|
||||
return createRole(rolesNames[1], aRole, null);
|
||||
}).then((anotherRole) => {
|
||||
roleIds[anotherRole.get("name")] = anotherRole.id;
|
||||
// set this role as a sibling of the last
|
||||
// the user should now have 3 roles
|
||||
return createRole(rolesNames[2], anotherRole, null);
|
||||
}).then((lastRole) => {
|
||||
roleIds[lastRole.get("name")] = lastRole.id;
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
|
||||
return auth._loadRoles();
|
||||
})
|
||||
}).then((roles) => {
|
||||
expect(roles.length).toEqual(3);
|
||||
rolesNames.forEach((name) => {
|
||||
expect(roles.indexOf('role:' + name)).not.toBe(-1);
|
||||
});
|
||||
done();
|
||||
}, function(){
|
||||
fail("should succeed")
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("_Role object should not save without name.", (done) => {
|
||||
var role = new Parse.Role();
|
||||
role.save(null,{useMasterKey:true})
|
||||
.then((r) => {
|
||||
fail("_Role object should not save without name.");
|
||||
}, (error) => {
|
||||
expect(error.code).toEqual(111);
|
||||
role.set('name','testRole');
|
||||
role.save(null,{useMasterKey:true})
|
||||
.then((r2)=>{
|
||||
fail("_Role object should not save without ACL.");
|
||||
}, (error2) =>{
|
||||
expect(error2.code).toEqual(111);
|
||||
done();
|
||||
.then(() => {
|
||||
fail("_Role object should not save without name.");
|
||||
}, (error) => {
|
||||
expect(error.code).toEqual(111);
|
||||
role.set('name','testRole');
|
||||
role.save(null,{useMasterKey:true})
|
||||
.then(()=>{
|
||||
fail("_Role object should not save without ACL.");
|
||||
}, (error2) =>{
|
||||
expect(error2.code).toEqual(111);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Different _Role objects cannot have the same name.", (done) => {
|
||||
const roleName = "MyRole";
|
||||
let aUser;
|
||||
createTestUser().then((user) => {
|
||||
aUser = user;
|
||||
return createRole(roleName, null, aUser);
|
||||
}).then((firstRole) => {
|
||||
expect(firstRole.getName()).toEqual(roleName);
|
||||
return createRole(roleName, null, aUser);
|
||||
}).then(() => {
|
||||
fail("_Role cannot have the same name as another role");
|
||||
done();
|
||||
}, (error) => {
|
||||
expect(error.code).toEqual(137);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Should properly resolve roles", (done) => {
|
||||
let admin = new Parse.Role("Admin", new Parse.ACL());
|
||||
let moderator = new Parse.Role("Moderator", new Parse.ACL());
|
||||
let superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
|
||||
let contentManager = new Parse.Role('ContentManager', new Parse.ACL());
|
||||
let superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
|
||||
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
|
||||
it("Should properly resolve roles", (done) => {
|
||||
const admin = new Parse.Role("Admin", new Parse.ACL());
|
||||
const moderator = new Parse.Role("Moderator", new Parse.ACL());
|
||||
const superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
|
||||
const contentManager = new Parse.Role('ContentManager', new Parse.ACL());
|
||||
const superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
|
||||
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
|
||||
contentManager.getRoles().add([moderator, superContentManager]);
|
||||
moderator.getRoles().add([admin, superModerator]);
|
||||
superContentManager.getRoles().add(superModerator);
|
||||
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true });
|
||||
// For each role, fetch their sibling, what they inherit
|
||||
// return with result and roleId for later comparison
|
||||
let promises = [admin, moderator, contentManager, superModerator].map((role) => {
|
||||
return auth._getAllRolesNamesForRoleIds([role.id]).then((result) => {
|
||||
const promises = [admin, moderator, contentManager, superModerator].map((role) => {
|
||||
return auth._getAllRolesNamesForRoleIds([role.id]).then((result) => {
|
||||
return Parse.Promise.as({
|
||||
id: role.id,
|
||||
name: role.get('name'),
|
||||
@@ -220,8 +238,8 @@ describe('Parse Role testing', () => {
|
||||
return Parse.Promise.when(promises);
|
||||
}).then((results) => {
|
||||
results.forEach((result) => {
|
||||
let id = result.id;
|
||||
let roleNames = result.roleNames;
|
||||
const id = result.id;
|
||||
const roleNames = result.roleNames;
|
||||
if (id == admin.id) {
|
||||
expect(roleNames.length).toBe(2);
|
||||
expect(roleNames.indexOf("Moderator")).not.toBe(-1);
|
||||
@@ -239,7 +257,7 @@ describe('Parse Role testing', () => {
|
||||
}
|
||||
});
|
||||
done();
|
||||
}).fail((err) => {
|
||||
}).fail(() => {
|
||||
done();
|
||||
})
|
||||
|
||||
@@ -250,16 +268,16 @@ describe('Parse Role testing', () => {
|
||||
roleACL.setPublicReadAccess(true);
|
||||
var role = new Parse.Role('subscribers', roleACL);
|
||||
role.save({}, {useMasterKey : true})
|
||||
.then((x)=>{
|
||||
.then(()=>{
|
||||
var query = role.relation('users').query();
|
||||
query.find({useMasterKey : true})
|
||||
.then((users)=>{
|
||||
.then(()=>{
|
||||
done();
|
||||
}, (e)=>{
|
||||
}, ()=>{
|
||||
fail('should not have errors');
|
||||
done();
|
||||
});
|
||||
}, (e) => {
|
||||
}, () => {
|
||||
fail('should not have errored');
|
||||
});
|
||||
});
|
||||
@@ -278,10 +296,10 @@ describe('Parse Role testing', () => {
|
||||
user = x;
|
||||
user2 = new Parse.User();
|
||||
return user2.save({ username: 'user2', password: 'omgbbq' });
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
user3 = new Parse.User();
|
||||
return user3.save({ username: 'user3', password: 'omgbbq' });
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
role = new Parse.Role('Admin', prACL);
|
||||
role.getUsers().add(user);
|
||||
return role.save({}, { useMasterKey: true });
|
||||
@@ -327,7 +345,7 @@ describe('Parse Role testing', () => {
|
||||
obj2 = new Parse.Object('TestObjectRoles');
|
||||
obj2.set('ACL', adminACL);
|
||||
return obj2.save(null, { useMasterKey: true });
|
||||
}, (e) => {
|
||||
}, () => {
|
||||
fail('Admin user should have been able to save.');
|
||||
done();
|
||||
}).then(() => {
|
||||
@@ -347,4 +365,166 @@ describe('Parse Role testing', () => {
|
||||
})
|
||||
});
|
||||
|
||||
it('should add multiple users to a role and remove users', (done) => {
|
||||
var user, user2, user3;
|
||||
var role;
|
||||
var obj;
|
||||
|
||||
var prACL = new Parse.ACL();
|
||||
prACL.setPublicReadAccess(true);
|
||||
prACL.setPublicWriteAccess(true);
|
||||
|
||||
createTestUser().then((x) => {
|
||||
user = x;
|
||||
user2 = new Parse.User();
|
||||
return user2.save({ username: 'user2', password: 'omgbbq' });
|
||||
}).then(() => {
|
||||
user3 = new Parse.User();
|
||||
return user3.save({ username: 'user3', password: 'omgbbq' });
|
||||
}).then(() => {
|
||||
role = new Parse.Role('sharedRole', prACL);
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
users.add(user2);
|
||||
users.add(user3);
|
||||
return role.save({}, { useMasterKey: true });
|
||||
}).then(() => {
|
||||
// query for saved role and get 3 users
|
||||
var query = new Parse.Query('_Role');
|
||||
query.equalTo('name', 'sharedRole');
|
||||
return query.find({ useMasterKey: true });
|
||||
}).then((role) => {
|
||||
expect(role.length).toEqual(1);
|
||||
var users = role[0].relation('users').query();
|
||||
return users.find({ useMasterKey: true });
|
||||
}).then((users) => {
|
||||
expect(users.length).toEqual(3);
|
||||
obj = new Parse.Object('TestObjectRoles');
|
||||
obj.set('ACL', prACL);
|
||||
return obj.save(null, { useMasterKey: true });
|
||||
}).then(() => {
|
||||
// Above, the Admin role was added to the Customer role.
|
||||
// An object secured by the Customer ACL should be able to be edited by the Admin user.
|
||||
obj.set('changedByUsers', true);
|
||||
return obj.save(null, { sessionToken: user.getSessionToken() });
|
||||
}).then(() => {
|
||||
// query for saved role and get 3 users
|
||||
var query = new Parse.Query('_Role');
|
||||
query.equalTo('name', 'sharedRole');
|
||||
return query.find({ useMasterKey: true });
|
||||
}).then((role) => {
|
||||
expect(role.length).toEqual(1);
|
||||
var users = role[0].relation('users');
|
||||
users.remove(user);
|
||||
users.remove(user3);
|
||||
return role[0].save({}, { useMasterKey: true });
|
||||
}).then((role) =>{
|
||||
var users = role.relation('users').query();
|
||||
return users.find({ useMasterKey: true });
|
||||
}).then((users) => {
|
||||
expect(users.length).toEqual(1);
|
||||
expect(users[0].get('username')).toEqual('user2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be secure (#3835)', (done) => {
|
||||
const acl = new Parse.ACL();
|
||||
acl.getPublicReadAccess(true);
|
||||
const role = new Parse.Role('admin', acl);
|
||||
role.save().then(() => {
|
||||
const user = new Parse.User();
|
||||
return user.signUp({username: 'hello', password: 'world'});
|
||||
}).then((user) => {
|
||||
role.getUsers().add(user)
|
||||
return role.save();
|
||||
}).then(done.fail, () => {
|
||||
const query = role.getUsers().query();
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(0);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should match when matching in users relation', (done) => {
|
||||
var user = new Parse.User();
|
||||
user
|
||||
.save({ username: 'admin', password: 'admin' })
|
||||
.then((user) => {
|
||||
var aCL = new Parse.ACL();
|
||||
aCL.setPublicReadAccess(true);
|
||||
aCL.setPublicWriteAccess(true);
|
||||
var role = new Parse.Role('admin', aCL);
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
role
|
||||
.save({}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
var query = new Parse.Query(Parse.Role);
|
||||
query.equalTo('name', 'admin');
|
||||
query.equalTo('users', user);
|
||||
query.find().then(function (roles) {
|
||||
expect(roles.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not match any entry when not matching in users relation', (done) => {
|
||||
var user = new Parse.User();
|
||||
user
|
||||
.save({ username: 'admin', password: 'admin' })
|
||||
.then((user) => {
|
||||
var aCL = new Parse.ACL();
|
||||
aCL.setPublicReadAccess(true);
|
||||
aCL.setPublicWriteAccess(true);
|
||||
var role = new Parse.Role('admin', aCL);
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
role
|
||||
.save({}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
var otherUser = new Parse.User();
|
||||
otherUser
|
||||
.save({ username: 'otherUser', password: 'otherUser' })
|
||||
.then((otherUser) => {
|
||||
var query = new Parse.Query(Parse.Role);
|
||||
query.equalTo('name', 'admin');
|
||||
query.equalTo('users', otherUser);
|
||||
query.find().then(function(roles) {
|
||||
expect(roles.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not match any entry when searching for null in users relation', (done) => {
|
||||
var user = new Parse.User();
|
||||
user
|
||||
.save({ username: 'admin', password: 'admin' })
|
||||
.then((user) => {
|
||||
var aCL = new Parse.ACL();
|
||||
aCL.setPublicReadAccess(true);
|
||||
aCL.setPublicWriteAccess(true);
|
||||
var role = new Parse.Role('admin', aCL);
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
role
|
||||
.save({}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
var query = new Parse.Query(Parse.Role);
|
||||
query.equalTo('name', 'admin');
|
||||
query.equalTo('users', null);
|
||||
query.find().then(function (roles) {
|
||||
expect(roles.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
const ParseServerRESTController = require('../src/ParseServerRESTController').ParseServerRESTController;
|
||||
const ParseServer = require('../src/ParseServer').default;
|
||||
const Parse = require('parse/node').Parse;
|
||||
|
||||
let RESTController;
|
||||
|
||||
describe('ParseServerRESTController', () => {
|
||||
describe('ParseServerRESTController', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(() => {
|
||||
RESTController = ParseServerRESTController(Parse.applicationId, ParseServer.promiseRouter({appId: Parse.applicationId}));
|
||||
})
|
||||
|
||||
it('should handle a get request', (done) => {
|
||||
RESTController.request("GET", "/classes/MyObject").then((res) => {
|
||||
it('should handle a get request', (done) => {
|
||||
RESTController.request("GET", "/classes/MyObject").then((res) => {
|
||||
expect(res.results.length).toBe(0);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a get request with full serverURL mount path', (done) => {
|
||||
RESTController.request("GET", "/1/classes/MyObject").then((res) => {
|
||||
it('should handle a get request with full serverURL mount path', (done) => {
|
||||
RESTController.request("GET", "/1/classes/MyObject").then((res) => {
|
||||
expect(res.results.length).toBe(0);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a POST batch', (done) => {
|
||||
it('should handle a POST batch', (done) => {
|
||||
RESTController.request("POST", "batch", {
|
||||
requests: [
|
||||
{
|
||||
@@ -46,74 +48,108 @@ describe('ParseServerRESTController', () => {
|
||||
path: '/classes/MyObject'
|
||||
}
|
||||
]
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(3);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a POST request', (done) => {
|
||||
RESTController.request("POST", "/classes/MyObject", {"key": "value"}).then((res) => {
|
||||
it('should handle a POST request', (done) => {
|
||||
RESTController.request("POST", "/classes/MyObject", {"key": "value"}).then(() => {
|
||||
return RESTController.request("GET", "/classes/MyObject");
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.results.length).toBe(1);
|
||||
expect(res.results[0].key).toEqual("value");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
}).fail((err) => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures sessionTokens are properly handled', (done) => {
|
||||
it('ensures sessionTokens are properly handled', (done) => {
|
||||
let userId;
|
||||
Parse.User.signUp('user', 'pass').then((user) => {
|
||||
Parse.User.signUp('user', 'pass').then((user) => {
|
||||
userId = user.id;
|
||||
let sessionToken = user.getSessionToken();
|
||||
const sessionToken = user.getSessionToken();
|
||||
return RESTController.request("GET", "/users/me", undefined, {sessionToken});
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
// Result is in JSON format
|
||||
expect(res.objectId).toEqual(userId);
|
||||
done();
|
||||
}).fail((err) => {
|
||||
}).fail((err) => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures masterKey is properly handled', (done) => {
|
||||
it('ensures masterKey is properly handled', (done) => {
|
||||
let userId;
|
||||
Parse.User.signUp('user', 'pass').then((user) => {
|
||||
Parse.User.signUp('user', 'pass').then((user) => {
|
||||
userId = user.id;
|
||||
let sessionToken = user.getSessionToken();
|
||||
return Parse.User.logOut().then(() => {
|
||||
return Parse.User.logOut().then(() => {
|
||||
return RESTController.request("GET", "/classes/_User", undefined, {useMasterKey: true});
|
||||
});
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.results.length).toBe(1);
|
||||
expect(res.results[0].objectId).toEqual(userId);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures no session token is created on creating users', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}).then(() => {
|
||||
let query = new Parse.Query('_Session');
|
||||
it('ensures no user is created when passing an empty username', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "", password: "world"}).then(() => {
|
||||
jfail(new Error('Success callback should not be called when passing an empty username.'));
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(Parse.Error.USERNAME_MISSING);
|
||||
expect(err.message).toBe('bad or missing username');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures no user is created when passing an empty password', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: ""}).then(() => {
|
||||
jfail(new Error('Success callback should not be called when passing an empty password.'));
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(Parse.Error.PASSWORD_MISSING);
|
||||
expect(err.message).toBe('password is required');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures no session token is created on creating users', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}).then((user) => {
|
||||
expect(user.sessionToken).toBeUndefined();
|
||||
const query = new Parse.Query('_Session');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then(sessions => {
|
||||
}).then(sessions => {
|
||||
expect(sessions.length).toBe(0);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('ensures a session token is created when passing installationId != cloud', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}, {installationId: 'my-installation'}).then((user) => {
|
||||
expect(user.sessionToken).not.toBeUndefined();
|
||||
const query = new Parse.Query('_Session');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then(sessions => {
|
||||
expect(sessions.length).toBe(1);
|
||||
expect(sessions[0].get('installationId')).toBe('my-installation');
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+770
-353
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,9 @@ describe('ParseWebSocketServer', function() {
|
||||
|
||||
it('can handle connect event when ws is open', function(done) {
|
||||
var onConnectCallback = jasmine.createSpy('onConnectCallback');
|
||||
var parseWebSocketServer = new ParseWebSocketServer({}, onConnectCallback, 5).server;
|
||||
var http = require('http');
|
||||
var server = http.createServer();
|
||||
var parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
|
||||
var ws = {
|
||||
readyState: 0,
|
||||
OPEN: 0,
|
||||
@@ -27,6 +29,7 @@ describe('ParseWebSocketServer', function() {
|
||||
// Make sure we ping to the client
|
||||
setTimeout(function() {
|
||||
expect(ws.ping).toHaveBeenCalled();
|
||||
server.close();
|
||||
done();
|
||||
}, 10)
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+253
-218
@@ -1,18 +1,16 @@
|
||||
'use strict';
|
||||
var Schema = require('../src/Controllers/SchemaController');
|
||||
|
||||
var Config = require('../src/Config');
|
||||
|
||||
describe('Pointer Permissions', () => {
|
||||
describe('Pointer Permissions', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
new Config(Parse.applicationId).database.schemaCache.clear();
|
||||
});
|
||||
|
||||
it('should work with find', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
it('should work with find', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -21,37 +19,37 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
let obj2 = new Parse.Object('AnObject');
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
obj.set('owner', user);
|
||||
obj2.set('owner', user2);
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {readUserFields: ['owner']})
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].id).toBe(obj.id);
|
||||
done();
|
||||
}).catch(error => {
|
||||
}).catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should work with write', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
it('should work with write', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -60,8 +58,8 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
let obj2 = new Parse.Object('AnObject');
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
obj.set('owner', user);
|
||||
@@ -69,16 +67,16 @@ describe('Pointer Permissions', () => {
|
||||
obj2.set('owner', user2);
|
||||
obj2.set('reader', user);
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {writeUserFields: ['owner'], readUserFields: ['reader', 'owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
obj2.set('hello', 'world');
|
||||
return obj2.save();
|
||||
}).then((res) => {
|
||||
}).then(() => {
|
||||
fail('User should not be able to update obj2');
|
||||
}, (err) => {
|
||||
// User 1 should not be able to update obj2
|
||||
@@ -87,19 +85,19 @@ describe('Pointer Permissions', () => {
|
||||
}).then(()=> {
|
||||
obj.set('hello', 'world');
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('User should be able to update');
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('should login with user 2');
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(2);
|
||||
res.forEach((result) => {
|
||||
res.forEach((result) => {
|
||||
if (result.id == obj.id) {
|
||||
expect(result.get('hello')).toBe('world');
|
||||
} else {
|
||||
@@ -107,16 +105,16 @@ describe('Pointer Permissions', () => {
|
||||
}
|
||||
})
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail("failed");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should let a proper user find', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -125,84 +123,58 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
let obj2 = new Parse.Object('AnObject');
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
user.signUp().then(() => {
|
||||
return user2.signUp()
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
Parse.User.logOut();
|
||||
}).then(() => {
|
||||
obj.set('owner', user);
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
|
||||
});
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
let q = new Parse.Query('AnObject');
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.get(obj.id);
|
||||
}).then(() => {
|
||||
fail('User 2 should not get the obj1 object');
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
expect(err.message).toBe('Object not found.');
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should not allow creating objects', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
user.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type:'Pointer', targetClass: '_User'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
obj.set('owner', user);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(119);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle multiple writeUserFields', done => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
it('should query on pointer permission enabled column', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -211,77 +183,144 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2])
|
||||
.then(() => {
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
user.signUp().then(() => {
|
||||
return user2.signUp()
|
||||
}).then(() => {
|
||||
Parse.User.logOut();
|
||||
}).then(() => {
|
||||
obj.set('owner', user);
|
||||
obj.set('otherOwner', user2);
|
||||
return obj.save();
|
||||
})
|
||||
.then(() => config.database.loadSchema())
|
||||
.then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
|
||||
.then(() => Parse.User.logIn('user1', 'password'))
|
||||
.then(() => obj.save({hello: 'fromUser1'}))
|
||||
.then(() => Parse.User.logIn('user2', 'password'))
|
||||
.then(() => obj.save({hello: 'fromUser2'}))
|
||||
.then(() => Parse.User.logOut())
|
||||
.then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
return q.first();
|
||||
})
|
||||
.then(result => {
|
||||
expect(result.get('hello')).toBe('fromUser2');
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
q.equalTo('owner', user2);
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
done();
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on missing field', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
it('should not allow creating objects', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
user.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type:'Pointer', targetClass: '_User'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
obj.set('owner', user);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(119);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle multiple writeUserFields', done => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2])
|
||||
.then(() => {
|
||||
obj.set('owner', user);
|
||||
obj.set('otherOwner', user2);
|
||||
return obj.save();
|
||||
})
|
||||
.then(() => config.database.loadSchema())
|
||||
.then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
|
||||
.then(() => Parse.User.logIn('user1', 'password'))
|
||||
.then(() => obj.save({hello: 'fromUser1'}))
|
||||
.then(() => Parse.User.logIn('user2', 'password'))
|
||||
.then(() => obj.save({hello: 'fromUser2'}))
|
||||
.then(() => Parse.User.logOut())
|
||||
.then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.first();
|
||||
})
|
||||
.then(result => {
|
||||
expect(result.get('hello')).toBe('fromUser2');
|
||||
done();
|
||||
}).catch(() => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on missing field', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(107);
|
||||
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on bad field', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
it('should prevent creating pointer permission on bad field', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'String'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(107);
|
||||
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on bad field', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let object = new Parse.Object('AnObject');
|
||||
it('should prevent creating pointer permission on bad field', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('owner', 'value');
|
||||
object.save().then(() => {
|
||||
object.save().then(() => {
|
||||
return config.database.loadSchema();
|
||||
}).then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}).catch((err) => {
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(107);
|
||||
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL write (PP Locked)', (done) => {
|
||||
it('tests CLP / Pointer Perms / ACL write (PP Locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: update closed ({})
|
||||
@@ -290,9 +329,9 @@ describe('Pointer Permissions', () => {
|
||||
|
||||
The owner is another user than the ACL
|
||||
*/
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -301,9 +340,9 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
let ACL = new Parse.ACL();
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
@@ -314,30 +353,30 @@ describe('Pointer Permissions', () => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be blocked by PP
|
||||
return obj.save({key: 'value'});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', (done) => {
|
||||
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: update closed ({})
|
||||
PointerPerm: "owner"
|
||||
ACL: logged in user has access
|
||||
*/
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -346,9 +385,9 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
let ACL = new Parse.ACL();
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
@@ -359,30 +398,30 @@ describe('Pointer Permissions', () => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be blocked by ACL
|
||||
return obj.save({key: 'value'});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', (done) => {
|
||||
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: update closed ({})
|
||||
PointerPerm: "owner"
|
||||
ACL: logged in user has access
|
||||
*/
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -391,9 +430,9 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
let ACL = new Parse.ACL();
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setWriteAccess(user, true);
|
||||
ACL.setWriteAccess(user2, true);
|
||||
obj.setACL(ACL);
|
||||
@@ -404,21 +443,21 @@ describe('Pointer Permissions', () => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be blocked by ACL
|
||||
return obj.save({key: 'value'});
|
||||
}).then((objAgain) => {
|
||||
}).then((objAgain) => {
|
||||
expect(objAgain.get('key')).toBe('value');
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('Should not fail saving');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL read (PP locked)', (done) => {
|
||||
it('tests CLP / Pointer Perms / ACL read (PP locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: find/get open ({})
|
||||
@@ -427,9 +466,9 @@ describe('Pointer Permissions', () => {
|
||||
|
||||
The owner is another user than the ACL
|
||||
*/
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -438,9 +477,9 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
let ACL = new Parse.ACL();
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
@@ -451,30 +490,30 @@ describe('Pointer Permissions', () => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {find: {}, get: {}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be block
|
||||
return obj.fetch();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', (done) => {
|
||||
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: find/get open ({"*": true})
|
||||
PointerPerm: "owner" : read
|
||||
ACL: logged in user has access
|
||||
*/
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -483,9 +522,9 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
let ACL = new Parse.ACL();
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
ACL.setReadAccess(user2, true);
|
||||
@@ -498,30 +537,30 @@ describe('Pointer Permissions', () => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be block
|
||||
return obj.fetch();
|
||||
}).then((objAgain) => {
|
||||
}).then((objAgain) => {
|
||||
expect(objAgain.id).toBe(obj.id);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('Should not fail fetching');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL read (ACL locked)', (done) => {
|
||||
it('tests CLP / Pointer Perms / ACL read (ACL locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: find/get open ({"*": true})
|
||||
PointerPerm: "owner" : read // proper owner
|
||||
ACL: logged in user has not access
|
||||
*/
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let user2 = new Parse.User();
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
@@ -530,9 +569,9 @@ describe('Pointer Permissions', () => {
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
let obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
let ACL = new Parse.ACL();
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
@@ -543,62 +582,60 @@ describe('Pointer Permissions', () => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// user2 has ACL read/write but should be block by ACL
|
||||
return obj.fetch();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should let master key find objects', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let object = new Parse.Object('AnObject');
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then(() => {
|
||||
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find({useMasterKey: true});
|
||||
}).then((objects) => {
|
||||
expect(objects.length).toBe(1);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('master key should find the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should let master key get objects', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let object = new Parse.Object('AnObject');
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.get(object.id);
|
||||
}).then(() => {
|
||||
|
||||
@@ -606,13 +643,13 @@ describe('Pointer Permissions', () => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('AnObject');
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.get(object.id, {useMasterKey: true});
|
||||
}).then((objectAgain) => {
|
||||
expect(objectAgain).not.toBeUndefined();
|
||||
expect(objectAgain.id).toBe(object.id);
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('master key should find the object');
|
||||
done();
|
||||
})
|
||||
@@ -620,11 +657,10 @@ describe('Pointer Permissions', () => {
|
||||
|
||||
|
||||
it('should let master key update objects', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let object = new Parse.Object('AnObject');
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {update: {}, writeUserFields: ['owner']});
|
||||
@@ -636,23 +672,22 @@ describe('Pointer Permissions', () => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return object.save({'hello': 'baz'}, {useMasterKey: true});
|
||||
}).then((objectAgain) => {
|
||||
expect(objectAgain.get('hello')).toBe('baz');
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('master key should save the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should let master key delete objects', (done) => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
let user = new Parse.User();
|
||||
let object = new Parse.Object('AnObject');
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner']});
|
||||
@@ -664,35 +699,35 @@ describe('Pointer Permissions', () => {
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
return object.destroy({useMasterKey: true});
|
||||
}).then((objectAgain) => {
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
}, () => {
|
||||
fail('master key should destroy the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should fail with invalid pointer perms', () => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
it('should fail with invalid pointer perms', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
|
||||
}).catch((err) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with invalid pointer perms', () => {
|
||||
let config = new Config(Parse.applicationId);
|
||||
it('should fail with invalid pointer perms', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});
|
||||
}).catch((err) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
const Parse = require('parse/node').Parse;
|
||||
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
const ParseServer = require("../src/index");
|
||||
const express = require('express');
|
||||
//public schema
|
||||
const databaseOptions1 = {
|
||||
initOptions: {
|
||||
connect: function (client, dc, isFresh) {
|
||||
if (isFresh) {
|
||||
client.query('SET search_path = public');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//not exists schema
|
||||
const databaseOptions2 = {
|
||||
initOptions: {
|
||||
connect: function (client, dc, isFresh) {
|
||||
if (isFresh) {
|
||||
client.query('SET search_path = not_exists_schema');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const GameScore = Parse.Object.extend({
|
||||
className: "GameScore"
|
||||
});
|
||||
|
||||
function createParseServer(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parseServer = new ParseServer.default(Object.assign({},
|
||||
defaultConfiguration, options, {
|
||||
serverURL: "http://localhost:12666/parse",
|
||||
__indexBuildCompletionCallbackForTests: promise => {
|
||||
promise
|
||||
.then(() => {
|
||||
expect(Parse.applicationId).toEqual("test");
|
||||
var app = express();
|
||||
app.use('/parse', parseServer.app);
|
||||
|
||||
const server = app.listen(12666);
|
||||
Parse.serverURL = "http://localhost:12666/parse";
|
||||
resolve(server);
|
||||
}, reject);
|
||||
}}));
|
||||
});
|
||||
}
|
||||
|
||||
describe_only_db('postgres')('Postgres database init options', () => {
|
||||
let server;
|
||||
|
||||
afterEach(() => {
|
||||
if (server) {
|
||||
server.close();
|
||||
}
|
||||
})
|
||||
|
||||
it('should create server with public schema databaseOptions', (done) => {
|
||||
const adapter = new PostgresStorageAdapter({
|
||||
uri: postgresURI, collectionPrefix: 'test_',
|
||||
databaseOptions: databaseOptions1
|
||||
})
|
||||
|
||||
createParseServer({ databaseAdapter: adapter }).then((newServer) => {
|
||||
server = newServer;
|
||||
var score = new GameScore({ "score": 1337, "playerName": "Sean Plott", "cheatMode": false });
|
||||
return score.save();
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should fail to create server if schema databaseOptions does not exist', (done) => {
|
||||
const adapter = new PostgresStorageAdapter({
|
||||
uri: postgresURI, collectionPrefix: 'test_',
|
||||
databaseOptions: databaseOptions2
|
||||
})
|
||||
|
||||
createParseServer({ databaseAdapter: adapter }).then(done.fail, done);
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,14 @@
|
||||
var PromiseRouter = require("../src/PromiseRouter").default;
|
||||
|
||||
describe("PromiseRouter", () => {
|
||||
it("should properly handle rejects", (done) => {
|
||||
describe("PromiseRouter", () => {
|
||||
it("should properly handle rejects", (done) => {
|
||||
var router = new PromiseRouter();
|
||||
router.route("GET", "/dummy", (req)=> {
|
||||
router.route("GET", "/dummy", ()=> {
|
||||
return Promise.reject({
|
||||
error: "an error",
|
||||
code: -1
|
||||
})
|
||||
}, (req) => {
|
||||
}, () => {
|
||||
fail("this should not be called");
|
||||
});
|
||||
|
||||
|
||||
+14
-14
@@ -3,7 +3,7 @@ var request = require('request');
|
||||
|
||||
describe("public API", () => {
|
||||
it("should get invalid_link.html", (done) => {
|
||||
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
@@ -14,23 +14,23 @@ describe("public API", () => {
|
||||
appName: 'unused',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it("should get verify_email_success.html", (done) => {
|
||||
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should get password_reset_success.html", (done) => {
|
||||
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
@@ -38,26 +38,26 @@ describe("public API", () => {
|
||||
});
|
||||
|
||||
describe("public API without publicServerURL", () => {
|
||||
beforeEach(done => {
|
||||
beforeEach(done => {
|
||||
reconfigureServer({ appName: 'unused' })
|
||||
.then(done, fail);
|
||||
.then(done, fail);
|
||||
});
|
||||
it("should get 404 on verify_email", (done) => {
|
||||
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should get 404 choose_password", (done) => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should get 404 on request_password_reset", (done) => {
|
||||
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(404);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -20,15 +20,15 @@ function createProduct() {
|
||||
}
|
||||
|
||||
describe("test validate_receipt endpoint", () => {
|
||||
beforeEach( done => {
|
||||
createProduct().then(done).fail(function(err){
|
||||
beforeEach(done => {
|
||||
createProduct().then(done).fail(function(){
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it("should bypass appstore validation", (done) => {
|
||||
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
@@ -45,7 +45,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
}, function(err, res, body){
|
||||
if (typeof body != "object") {
|
||||
fail("Body is not an object");
|
||||
done();
|
||||
done();
|
||||
} else {
|
||||
expect(body.__type).toEqual("File");
|
||||
const url = body.url;
|
||||
@@ -60,7 +60,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
});
|
||||
|
||||
it("should fail for missing receipt", (done) => {
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
@@ -73,7 +73,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
}, function(err, res, body){
|
||||
if (typeof body != "object") {
|
||||
fail("Body is not an object");
|
||||
done();
|
||||
done();
|
||||
} else {
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
@@ -82,7 +82,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
});
|
||||
|
||||
it("should fail for missing product identifier", (done) => {
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
@@ -98,7 +98,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
}, function(err, res, body){
|
||||
if (typeof body != "object") {
|
||||
fail("Body is not an object");
|
||||
done();
|
||||
done();
|
||||
} else {
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
@@ -108,7 +108,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
|
||||
it("should bypass appstore validation and not find product", (done) => {
|
||||
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
@@ -135,7 +135,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
});
|
||||
|
||||
it("should fail at appstore validation", done => {
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
@@ -160,49 +160,49 @@ describe("test validate_receipt endpoint", () => {
|
||||
});
|
||||
|
||||
it("should not create a _Product", (done) => {
|
||||
var product = new Parse.Object("_Product");
|
||||
product.save().then(function(){
|
||||
fail("Should not be able to save");
|
||||
done();
|
||||
}, function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
done();
|
||||
})
|
||||
var product = new Parse.Object("_Product");
|
||||
product.save().then(function(){
|
||||
fail("Should not be able to save");
|
||||
done();
|
||||
}, function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should be able to update a _Product", (done) => {
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product) {
|
||||
if (!product) {
|
||||
return Promise.reject(new Error('Product should be found'));
|
||||
}
|
||||
product.set("title", "a new title");
|
||||
return product.save();
|
||||
}).then(function(productAgain){
|
||||
expect(productAgain.get('downloadName')).toEqual(productAgain.get('download').name());
|
||||
expect(productAgain.get("title")).toEqual("a new title");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product) {
|
||||
if (!product) {
|
||||
return Promise.reject(new Error('Product should be found'));
|
||||
}
|
||||
product.set("title", "a new title");
|
||||
return product.save();
|
||||
}).then(function(productAgain){
|
||||
expect(productAgain.get('downloadName')).toEqual(productAgain.get('download').name());
|
||||
expect(productAgain.get("title")).toEqual("a new title");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not be able to remove a require key in a _Product", (done) => {
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product){
|
||||
if (!product) {
|
||||
return Promise.reject(new Error('Product should be found'));
|
||||
}
|
||||
product.unset("title");
|
||||
return product.save();
|
||||
}).then(function(productAgain){
|
||||
fail("Should not succeed");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(err.message).toEqual("title is required.");
|
||||
done();
|
||||
});
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product){
|
||||
if (!product) {
|
||||
return Promise.reject(new Error('Product should be found'));
|
||||
}
|
||||
product.unset("title");
|
||||
return product.save();
|
||||
}).then(function(){
|
||||
fail("Should not succeed");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(err.message).toEqual("title is required.");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+1041
-285
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
import Config from "../src/Config";
|
||||
import {PushQueue} from "../src/Push/PushQueue";
|
||||
|
||||
describe('PushQueue', () => {
|
||||
describe('With a defined channel', () => {
|
||||
it('should be propagated to the PushWorker and PushQueue', (done) => {
|
||||
reconfigureServer({
|
||||
push: {
|
||||
queueOptions: {
|
||||
disablePushWorker: false,
|
||||
channel: 'my-specific-channel'
|
||||
},
|
||||
adapter: {
|
||||
send() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getValidPushTypes() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
expect(config.pushWorker.channel).toEqual('my-specific-channel', 'pushWorker.channel');
|
||||
expect(config.pushControllerQueue.channel).toEqual('my-specific-channel', 'pushWorker.channel');
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Default channel', () => {
|
||||
it('should be prefixed with the applicationId', (done) => {
|
||||
reconfigureServer({
|
||||
push: {
|
||||
queueOptions: {
|
||||
disablePushWorker: false
|
||||
},
|
||||
adapter: {
|
||||
send() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getValidPushTypes() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
expect(PushQueue.defaultPushChannel()).toEqual('test-parse-server-push');
|
||||
expect(config.pushWorker.channel).toEqual('test-parse-server-push');
|
||||
expect(config.pushControllerQueue.channel).toEqual('test-parse-server-push');
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -70,7 +70,7 @@ describe('PushRouter', () => {
|
||||
|
||||
it('sends a push through REST', (done) => {
|
||||
request.post({
|
||||
url: Parse.serverURL+"/push",
|
||||
url: Parse.serverURL + "/push",
|
||||
json: true,
|
||||
body: {
|
||||
'channels': {
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
var PushWorker = require('../src').PushWorker;
|
||||
var PushUtils = require('../src/Push/utils');
|
||||
var Config = require('../src/Config');
|
||||
var { pushStatusHandler } = require('../src/StatusHandler');
|
||||
|
||||
describe('PushWorker', () => {
|
||||
it('should run with small batch', (done) => {
|
||||
const batchSize = 3;
|
||||
var sendCount = 0;
|
||||
reconfigureServer({
|
||||
push: {
|
||||
queueOptions: {
|
||||
disablePushWorker: true,
|
||||
batchSize
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
expect(new Config('test').pushWorker).toBeUndefined();
|
||||
new PushWorker({
|
||||
send: (body, installations) => {
|
||||
expect(installations.length <= batchSize).toBe(true);
|
||||
sendCount += installations.length;
|
||||
return Promise.resolve();
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ['ios', 'android']
|
||||
}
|
||||
});
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", 1);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
}).then(() => {
|
||||
expect(sendCount).toBe(10);
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
})
|
||||
});
|
||||
|
||||
describe('localized push', () => {
|
||||
it('should return locales', () => {
|
||||
const locales = PushUtils.getLocalesFromPush({
|
||||
data: {
|
||||
'alert-fr': 'french',
|
||||
'alert': 'Yo!',
|
||||
'alert-en-US': 'English',
|
||||
}
|
||||
});
|
||||
expect(locales).toEqual(['fr', 'en-US']);
|
||||
});
|
||||
|
||||
it('should return and empty array if no locale is set', () => {
|
||||
const locales = PushUtils.getLocalesFromPush({
|
||||
data: {
|
||||
'alert': 'Yo!',
|
||||
}
|
||||
});
|
||||
expect(locales).toEqual([]);
|
||||
});
|
||||
|
||||
it('should deduplicate locales', () => {
|
||||
const locales = PushUtils.getLocalesFromPush({
|
||||
data: {
|
||||
'alert': 'Yo!',
|
||||
'alert-fr': 'french',
|
||||
'title-fr': 'french'
|
||||
}
|
||||
});
|
||||
expect(locales).toEqual(['fr']);
|
||||
});
|
||||
|
||||
it('transforms body appropriately', () => {
|
||||
const cleanBody = PushUtils.transformPushBodyForLocale({
|
||||
data: {
|
||||
alert: 'Yo!',
|
||||
'alert-fr': 'frenchy!',
|
||||
'alert-en': 'english',
|
||||
}
|
||||
}, 'fr');
|
||||
expect(cleanBody).toEqual({
|
||||
data: {
|
||||
alert: 'frenchy!'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('transforms body appropriately', () => {
|
||||
const cleanBody = PushUtils.transformPushBodyForLocale({
|
||||
data: {
|
||||
alert: 'Yo!',
|
||||
'alert-fr': 'frenchy!',
|
||||
'alert-en': 'english',
|
||||
'title-fr': 'french title'
|
||||
}
|
||||
}, 'fr');
|
||||
expect(cleanBody).toEqual({
|
||||
data: {
|
||||
alert: 'frenchy!',
|
||||
title: 'french title'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('maps body on all provided locales', () => {
|
||||
const bodies = PushUtils.bodiesPerLocales({
|
||||
data: {
|
||||
alert: 'Yo!',
|
||||
'alert-fr': 'frenchy!',
|
||||
'alert-en': 'english',
|
||||
'title-fr': 'french title'
|
||||
}
|
||||
}, ['fr', 'en']);
|
||||
expect(bodies).toEqual({
|
||||
fr: {
|
||||
data: {
|
||||
alert: 'frenchy!',
|
||||
title: 'french title'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
data: {
|
||||
alert: 'english',
|
||||
}
|
||||
},
|
||||
default: {
|
||||
data: {
|
||||
alert: 'Yo!'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly handle default cases', () => {
|
||||
expect(PushUtils.transformPushBodyForLocale({})).toEqual({});
|
||||
expect(PushUtils.stripLocalesFromBody({})).toEqual({});
|
||||
expect(PushUtils.bodiesPerLocales({where: {}})).toEqual({default: {where: {}}});
|
||||
expect(PushUtils.groupByLocaleIdentifier([])).toEqual({default: []});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pushStatus', () => {
|
||||
it('should remove invalid installations', (done) => {
|
||||
const config = new Config('test');
|
||||
const handler = pushStatusHandler(config);
|
||||
const spy = spyOn(config.database, "update").and.callFake(() => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
const toAwait = handler.trackSent([
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'Unregistered' }
|
||||
},
|
||||
{
|
||||
transmitted: true,
|
||||
device: {
|
||||
deviceToken: 10,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
},
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 2,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'NotRegistered' }
|
||||
},
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 3,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'InvalidRegistration' }
|
||||
},
|
||||
{
|
||||
transmitted: true,
|
||||
device: {
|
||||
deviceToken: 11,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
},
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 4,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'InvalidRegistration' }
|
||||
},
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 5,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'InvalidRegistration' }
|
||||
},
|
||||
{ // should not be deleted
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 101,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'invalid error...' }
|
||||
}
|
||||
], undefined, true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy.calls.count()).toBe(1);
|
||||
const lastCall = spy.calls.mostRecent();
|
||||
expect(lastCall.args[0]).toBe('_Installation');
|
||||
expect(lastCall.args[1]).toEqual({
|
||||
deviceToken: { '$in': [1,2,3,4,5] }
|
||||
});
|
||||
expect(lastCall.args[2]).toEqual({
|
||||
deviceToken: { '__op': "Delete" }
|
||||
});
|
||||
toAwait.then(done).catch(done);
|
||||
});
|
||||
|
||||
it('tracks push status per UTC offsets', (done) => {
|
||||
const config = new Config('test');
|
||||
const handler = pushStatusHandler(config);
|
||||
const spy = spyOn(Parse, "_request").and.callThrough();
|
||||
const UTCOffset = 1;
|
||||
handler.setInitial().then(() => {
|
||||
return handler.trackSent([
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
},
|
||||
{
|
||||
transmitted: true,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
}
|
||||
},
|
||||
], UTCOffset)
|
||||
}).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
const lastCall = spy.calls.mostRecent();
|
||||
expect(lastCall.args[0]).toBe('PUT');
|
||||
expect(lastCall.args[1]).toBe(`classes/_PushStatus/${handler.objectId}`);
|
||||
expect(lastCall.args[2]).toEqual({
|
||||
numSent: { __op: 'Increment', amount: 1 },
|
||||
numFailed: { __op: 'Increment', amount: 1 },
|
||||
'sentPerType.ios': { __op: 'Increment', amount: 1 },
|
||||
'failedPerType.ios': { __op: 'Increment', amount: 1 },
|
||||
[`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
|
||||
[`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
|
||||
count: { __op: 'Increment', amount: -2 },
|
||||
});
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.get(handler.objectId, { useMasterKey: true });
|
||||
}).then((pushStatus) => {
|
||||
const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
|
||||
expect(sentPerUTCOffset['1']).toBe(1);
|
||||
const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
|
||||
expect(failedPerUTCOffset['1']).toBe(1);
|
||||
return handler.trackSent([
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
},
|
||||
{
|
||||
transmitted: true,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
}
|
||||
},
|
||||
{
|
||||
transmitted: true,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
}
|
||||
},
|
||||
], UTCOffset)
|
||||
}).then(() => {
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.get(handler.objectId, { useMasterKey: true });
|
||||
}).then((pushStatus) => {
|
||||
const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
|
||||
expect(sentPerUTCOffset['1']).toBe(3);
|
||||
const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
|
||||
expect(failedPerUTCOffset['1']).toBe(2);
|
||||
}).then(done).catch(done.fail);
|
||||
});
|
||||
|
||||
it('tracks push status per UTC offsets with negative offsets', (done) => {
|
||||
const config = new Config('test');
|
||||
const handler = pushStatusHandler(config);
|
||||
spyOn(config.database, "create").and.callFake(() => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
const spy = spyOn(Parse, "_request").and.callFake(() => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
const UTCOffset = -6;
|
||||
handler.trackSent([
|
||||
{
|
||||
transmitted: false,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'Unregistered' }
|
||||
},
|
||||
{
|
||||
transmitted: true,
|
||||
device: {
|
||||
deviceToken: 1,
|
||||
deviceType: 'ios',
|
||||
},
|
||||
response: { error: 'Unregistered' }
|
||||
},
|
||||
], UTCOffset).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
const lastCall = spy.calls.mostRecent();
|
||||
expect(lastCall.args[1]).toBe(`classes/_PushStatus/${handler.objectId}`);
|
||||
expect(lastCall.args[2]).toEqual({
|
||||
numSent: { __op: 'Increment', amount: 1 },
|
||||
numFailed: { __op: 'Increment', amount: 1 },
|
||||
'sentPerType.ios': { __op: 'Increment', amount: 1 },
|
||||
'failedPerType.ios': { __op: 'Increment', amount: 1 },
|
||||
[`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
|
||||
[`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
|
||||
count: { __op: 'Increment', amount: -2 },
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+101
-1
@@ -226,6 +226,23 @@ describe('matchesQuery', function() {
|
||||
|
||||
img.owner.objectId = 'U3';
|
||||
expect(matchesQuery(img, q)).toBe(false);
|
||||
|
||||
// pointers in arrays
|
||||
q = new Parse.Query('Image');
|
||||
q.equalTo('owners', u);
|
||||
|
||||
img = {
|
||||
className: 'Image',
|
||||
objectId: 'I1',
|
||||
owners: [{
|
||||
className: '_User',
|
||||
objectId: 'U2'
|
||||
}]
|
||||
};
|
||||
expect(matchesQuery(img, q)).toBe(true);
|
||||
|
||||
img.owners[0].objectId = 'U3';
|
||||
expect(matchesQuery(img, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on inequalities', function() {
|
||||
@@ -343,7 +360,16 @@ describe('matchesQuery', function() {
|
||||
id: new Id('Checkin', 'C1'),
|
||||
location: new Parse.GeoPoint(40, 40)
|
||||
};
|
||||
var ptUndefined = {
|
||||
id: new Id('Checkin', 'C1')
|
||||
};
|
||||
var ptNull = {
|
||||
id: new Id('Checkin', 'C1'),
|
||||
location: null
|
||||
};
|
||||
expect(matchesQuery(pt, q)).toBe(true);
|
||||
expect(matchesQuery(ptUndefined, q)).toBe(false);
|
||||
expect(matchesQuery(ptNull, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Checkin');
|
||||
pt.location = new Parse.GeoPoint(40, 40);
|
||||
@@ -367,6 +393,17 @@ describe('matchesQuery', function() {
|
||||
name: 'Santa Clara'
|
||||
};
|
||||
|
||||
var noLocation = {
|
||||
id: new Id('Checkin', 'C2'),
|
||||
name: 'Santa Clara'
|
||||
};
|
||||
|
||||
var nullLocation = {
|
||||
id: new Id('Checkin', 'C2'),
|
||||
location: null,
|
||||
name: 'Santa Clara'
|
||||
};
|
||||
|
||||
var q = new Parse.Query('Checkin').withinGeoBox(
|
||||
'location',
|
||||
new Parse.GeoPoint(37.708813, -122.526398),
|
||||
@@ -375,7 +412,8 @@ describe('matchesQuery', function() {
|
||||
|
||||
expect(matchesQuery(caltrainStation, q)).toBe(true);
|
||||
expect(matchesQuery(santaClara, q)).toBe(false);
|
||||
|
||||
expect(matchesQuery(noLocation, q)).toBe(false);
|
||||
expect(matchesQuery(nullLocation, q)).toBe(false);
|
||||
// Invalid rectangles
|
||||
q = new Parse.Query('Checkin').withinGeoBox(
|
||||
'location',
|
||||
@@ -395,4 +433,66 @@ describe('matchesQuery', function() {
|
||||
expect(matchesQuery(caltrainStation, q)).toBe(false);
|
||||
expect(matchesQuery(santaClara, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on subobjects with dot notation', function() {
|
||||
var message = {
|
||||
id: new Id('Message', 'O1'),
|
||||
text: "content",
|
||||
status: {x: "read", y: "delivered"}
|
||||
};
|
||||
|
||||
var q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "read");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.z", "read");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "delivered");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.x", "read");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.z", "read");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.x", "delivered");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("status.x");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("status.z");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("nonexistent.x");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("status.x");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("status.z");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("nonexistent.z");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "read");
|
||||
q.doesNotExist("status.y");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,748 @@
|
||||
'use strict'
|
||||
|
||||
const Parse = require('parse/node');
|
||||
const ReadPreference = require('mongodb').ReadPreference;
|
||||
const rp = require('request-promise');
|
||||
const Config = require("../src/Config");
|
||||
|
||||
describe_only_db('mongo')('Read preference option', () => {
|
||||
it('should find in primary by default', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.PRIMARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference in the beforeFind trigger', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference in the beforeFind trigger even changing query', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.query.equalTo('boolKey', true);
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(true);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference in the beforeFind trigger even returning query', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
|
||||
const otherQuery = new Parse.Query('MyObject');
|
||||
otherQuery.equalTo('boolKey', true);
|
||||
return otherQuery;
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(true);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference in the beforeFind trigger even returning promise', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
|
||||
const otherQuery = new Parse.Query('MyObject');
|
||||
otherQuery.equalTo('boolKey', true);
|
||||
return Promise.resolve(otherQuery);
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(true);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference to PRIMARY_PREFERRED', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'PRIMARY_PREFERRED';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.PRIMARY_PREFERRED);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference to SECONDARY_PREFERRED', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY_PREFERRED';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY_PREFERRED);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference to NEAREST', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'NEAREST';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.NEAREST);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference for GET', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
|
||||
query.get(obj0.id).then((result) => {
|
||||
expect(result.get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference for GET using API', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
rp({
|
||||
method: 'GET',
|
||||
uri: 'http://localhost:8378/1/classes/MyObject/' + obj0.id,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
},
|
||||
json: true,
|
||||
}).then(body => {
|
||||
expect(body.boolKey).toBe(false);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject') >= 0) {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change read preference for count', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('boolKey', true);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'command').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject');
|
||||
query.equalTo('boolKey', false);
|
||||
|
||||
query.count().then((result) => {
|
||||
expect(result).toBe(1);
|
||||
|
||||
let myObjectReadPreference = null;
|
||||
databaseAdapter.database.serverConfig.command.calls.all().forEach((call) => {
|
||||
myObjectReadPreference = call.args[2].readPreference.preference;
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should find includes in primary by default', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject0');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject1');
|
||||
obj1.set('boolKey', true);
|
||||
obj1.set('myObject0', obj0);
|
||||
const obj2 = new Parse.Object('MyObject2');
|
||||
obj2.set('boolKey', false);
|
||||
obj2.set('myObject1', obj1);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject2', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject2');
|
||||
query.equalTo('boolKey', false);
|
||||
query.include('myObject1');
|
||||
query.include('myObject1.myObject0');
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
const firstResult = results[0];
|
||||
expect(firstResult.get('boolKey')).toBe(false);
|
||||
expect(firstResult.get('myObject1').get('boolKey')).toBe(true);
|
||||
expect(firstResult.get('myObject1').get('myObject0').get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference0 = null;
|
||||
let myObjectReadPreference1 = null;
|
||||
let myObjectReadPreference2 = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject0') >= 0) {
|
||||
myObjectReadPreference0 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject1') >= 0) {
|
||||
myObjectReadPreference1 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject2') >= 0) {
|
||||
myObjectReadPreference2 = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference0).toEqual(ReadPreference.PRIMARY);
|
||||
expect(myObjectReadPreference1).toEqual(ReadPreference.PRIMARY);
|
||||
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change includes read preference', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject0');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject1');
|
||||
obj1.set('boolKey', true);
|
||||
obj1.set('myObject0', obj0);
|
||||
const obj2 = new Parse.Object('MyObject2');
|
||||
obj2.set('boolKey', false);
|
||||
obj2.set('myObject1', obj1);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject2', (req) => {
|
||||
req.readPreference = 'SECONDARY_PREFERRED';
|
||||
req.includeReadPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query = new Parse.Query('MyObject2');
|
||||
query.equalTo('boolKey', false);
|
||||
query.include('myObject1');
|
||||
query.include('myObject1.myObject0');
|
||||
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
const firstResult = results[0];
|
||||
expect(firstResult.get('boolKey')).toBe(false);
|
||||
expect(firstResult.get('myObject1').get('boolKey')).toBe(true);
|
||||
expect(firstResult.get('myObject1').get('myObject0').get('boolKey')).toBe(false);
|
||||
|
||||
|
||||
let myObjectReadPreference0 = null;
|
||||
let myObjectReadPreference1 = null;
|
||||
let myObjectReadPreference2 = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject0') >= 0) {
|
||||
myObjectReadPreference0 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject1') >= 0) {
|
||||
myObjectReadPreference1 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject2') >= 0) {
|
||||
myObjectReadPreference2 = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should find subqueries in primary by default', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject0');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject1');
|
||||
obj1.set('boolKey', true);
|
||||
obj1.set('myObject0', obj0);
|
||||
const obj2 = new Parse.Object('MyObject2');
|
||||
obj2.set('boolKey', false);
|
||||
obj2.set('myObject1', obj1);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject2', (req) => {
|
||||
req.readPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query0 = new Parse.Query('MyObject0');
|
||||
query0.equalTo('boolKey', false);
|
||||
|
||||
const query1 = new Parse.Query('MyObject1');
|
||||
query1.matchesQuery('myObject0', query0);
|
||||
|
||||
const query2 = new Parse.Query('MyObject2');
|
||||
query2.matchesQuery('myObject1', query1);
|
||||
|
||||
query2.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference0 = null;
|
||||
let myObjectReadPreference1 = null;
|
||||
let myObjectReadPreference2 = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject0') >= 0) {
|
||||
myObjectReadPreference0 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject1') >= 0) {
|
||||
myObjectReadPreference1 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject2') >= 0) {
|
||||
myObjectReadPreference2 = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference0).toEqual(ReadPreference.PRIMARY);
|
||||
expect(myObjectReadPreference1).toEqual(ReadPreference.PRIMARY);
|
||||
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change subqueries read preference when using matchesQuery', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject0');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject1');
|
||||
obj1.set('boolKey', true);
|
||||
obj1.set('myObject0', obj0);
|
||||
const obj2 = new Parse.Object('MyObject2');
|
||||
obj2.set('boolKey', false);
|
||||
obj2.set('myObject1', obj1);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject2', (req) => {
|
||||
req.readPreference = 'SECONDARY_PREFERRED';
|
||||
req.subqueryReadPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query0 = new Parse.Query('MyObject0');
|
||||
query0.equalTo('boolKey', false);
|
||||
|
||||
const query1 = new Parse.Query('MyObject1');
|
||||
query1.matchesQuery('myObject0', query0);
|
||||
|
||||
const query2 = new Parse.Query('MyObject2');
|
||||
query2.matchesQuery('myObject1', query1);
|
||||
|
||||
query2.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference0 = null;
|
||||
let myObjectReadPreference1 = null;
|
||||
let myObjectReadPreference2 = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject0') >= 0) {
|
||||
myObjectReadPreference0 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject1') >= 0) {
|
||||
myObjectReadPreference1 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject2') >= 0) {
|
||||
myObjectReadPreference2 = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change subqueries read preference when using doesNotMatchQuery', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject0');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject1');
|
||||
obj1.set('boolKey', true);
|
||||
obj1.set('myObject0', obj0);
|
||||
const obj2 = new Parse.Object('MyObject2');
|
||||
obj2.set('boolKey', false);
|
||||
obj2.set('myObject1', obj1);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject2', (req) => {
|
||||
req.readPreference = 'SECONDARY_PREFERRED';
|
||||
req.subqueryReadPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query0 = new Parse.Query('MyObject0');
|
||||
query0.equalTo('boolKey', false);
|
||||
|
||||
const query1 = new Parse.Query('MyObject1');
|
||||
query1.doesNotMatchQuery('myObject0', query0);
|
||||
|
||||
const query2 = new Parse.Query('MyObject2');
|
||||
query2.doesNotMatchQuery('myObject1', query1);
|
||||
|
||||
query2.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference0 = null;
|
||||
let myObjectReadPreference1 = null;
|
||||
let myObjectReadPreference2 = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject0') >= 0) {
|
||||
myObjectReadPreference0 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject1') >= 0) {
|
||||
myObjectReadPreference1 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject2') >= 0) {
|
||||
myObjectReadPreference2 = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change subqueries read preference when using matchesKeyInQuery and doesNotMatchKeyInQuery', (done) => {
|
||||
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
|
||||
|
||||
const obj0 = new Parse.Object('MyObject0');
|
||||
obj0.set('boolKey', false);
|
||||
const obj1 = new Parse.Object('MyObject1');
|
||||
obj1.set('boolKey', true);
|
||||
obj1.set('myObject0', obj0);
|
||||
const obj2 = new Parse.Object('MyObject2');
|
||||
obj2.set('boolKey', false);
|
||||
obj2.set('myObject1', obj1);
|
||||
|
||||
Parse.Object.saveAll([obj0, obj1, obj2]).then(() => {
|
||||
spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough();
|
||||
|
||||
Parse.Cloud.beforeFind('MyObject2', (req) => {
|
||||
req.readPreference = 'SECONDARY_PREFERRED';
|
||||
req.subqueryReadPreference = 'SECONDARY';
|
||||
});
|
||||
|
||||
const query0 = new Parse.Query('MyObject0');
|
||||
query0.equalTo('boolKey', false);
|
||||
|
||||
const query1 = new Parse.Query('MyObject1');
|
||||
query1.equalTo('boolKey', true);
|
||||
|
||||
const query2 = new Parse.Query('MyObject2');
|
||||
query2.matchesKeyInQuery('boolKey', 'boolKey', query0);
|
||||
query2.doesNotMatchKeyInQuery('boolKey', 'boolKey', query1);
|
||||
|
||||
query2.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].get('boolKey')).toBe(false);
|
||||
|
||||
let myObjectReadPreference0 = null;
|
||||
let myObjectReadPreference1 = null;
|
||||
let myObjectReadPreference2 = null;
|
||||
databaseAdapter.database.serverConfig.cursor.calls.all().forEach((call) => {
|
||||
if (call.args[0].indexOf('MyObject0') >= 0) {
|
||||
myObjectReadPreference0 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject1') >= 0) {
|
||||
myObjectReadPreference1 = call.args[2].readPreference.preference;
|
||||
}
|
||||
if (call.args[0].indexOf('MyObject2') >= 0) {
|
||||
myObjectReadPreference2 = call.args[2].readPreference.preference;
|
||||
}
|
||||
});
|
||||
|
||||
expect(myObjectReadPreference0).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference1).toEqual(ReadPreference.SECONDARY);
|
||||
expect(myObjectReadPreference2).toEqual(ReadPreference.SECONDARY_PREFERRED);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
var RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
|
||||
/*
|
||||
To run this test part of the complete suite
|
||||
set PARSE_SERVER_TEST_CACHE='redis'
|
||||
and make sure a redis server is available on the default port
|
||||
*/
|
||||
describe_only(() => {
|
||||
return process.env.PARSE_SERVER_TEST_CACHE === 'redis';
|
||||
})('RedisCacheAdapter', function() {
|
||||
var KEY = 'hello';
|
||||
var VALUE = 'world';
|
||||
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
}
|
||||
|
||||
it('should get/set/clear', (done) => {
|
||||
var cache = new RedisCacheAdapter({
|
||||
ttl: NaN
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(() => cache.clear())
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('should expire after ttl', (done) => {
|
||||
var cache = new RedisCacheAdapter(null, 1);
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(wait.bind(null, 2))
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('should find un-expired records', (done) => {
|
||||
var cache = new RedisCacheAdapter(null, 5);
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(wait.bind(null, 1))
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).not.toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
});
|
||||
@@ -10,14 +10,14 @@ describe('RedisPubSub', function() {
|
||||
});
|
||||
|
||||
it('can create publisher', function() {
|
||||
var publisher = RedisPubSub.createPublisher({redisURL: 'redisAddress'});
|
||||
RedisPubSub.createPublisher({redisURL: 'redisAddress'});
|
||||
|
||||
var redis = require('redis');
|
||||
expect(redis.createClient).toHaveBeenCalledWith('redisAddress', { no_ready_check: true });
|
||||
});
|
||||
|
||||
it('can create subscriber', function() {
|
||||
var subscriber = RedisPubSub.createSubscriber({redisURL: 'redisAddress'});
|
||||
RedisPubSub.createSubscriber({redisURL: 'redisAddress'});
|
||||
|
||||
var redis = require('redis');
|
||||
expect(redis.createClient).toHaveBeenCalledWith('redisAddress', { no_ready_check: true });
|
||||
|
||||
+51
-53
@@ -1,12 +1,10 @@
|
||||
'use strict'
|
||||
// These tests check the "find" functionality of the REST API.
|
||||
var auth = require('../src/Auth');
|
||||
var cache = require('../src/cache');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
|
||||
var querystring = require('querystring');
|
||||
var request = require('request');
|
||||
var rp = require('request-promise');
|
||||
|
||||
var config;
|
||||
@@ -15,7 +13,7 @@ var nobody = auth.nobody(config);
|
||||
|
||||
describe('rest query', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(() => {
|
||||
config = new Config('test');
|
||||
database = config.database;
|
||||
});
|
||||
@@ -33,10 +31,10 @@ describe('rest query', () => {
|
||||
rest.create(config, nobody, 'TestObject', {foo: 'baz'}
|
||||
).then(() => {
|
||||
return rest.create(config, nobody,
|
||||
'TestObject', {foo: 'qux'});
|
||||
'TestObject', {foo: 'qux'});
|
||||
}).then(() => {
|
||||
return rest.find(config, nobody,
|
||||
'TestObject', {}, {limit: 1});
|
||||
'TestObject', {}, {limit: 1});
|
||||
}).then((response) => {
|
||||
expect(response.results.length).toEqual(1);
|
||||
expect(response.results[0].foo).toBeTruthy();
|
||||
@@ -118,11 +116,11 @@ describe('rest query', () => {
|
||||
activity.photo.objectId = photo.objectId;
|
||||
activity.fromUser.objectId = user.objectId;
|
||||
return rest.create(config, nobody,
|
||||
'TestActivity', activity);
|
||||
'TestActivity', activity);
|
||||
}).then(() => {
|
||||
queryWhere.photo.objectId = photo.objectId;
|
||||
return rest.find(config, nobody,
|
||||
'TestActivity', queryWhere, queryOptions);
|
||||
'TestActivity', queryWhere, queryOptions);
|
||||
}).then((response) => {
|
||||
var results = response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -145,62 +143,62 @@ describe('rest query', () => {
|
||||
expect(err.message).toEqual('This user is not allowed to access ' +
|
||||
'non-existent class: ClientClassCreation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('query existent class when disabled client class creation', (done) => {
|
||||
var customConfig = Object.assign({}, config, {allowClientClassCreation: false});
|
||||
config.database.loadSchema()
|
||||
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
|
||||
.then(actualSchema => {
|
||||
expect(actualSchema.className).toEqual('ClientClassCreation');
|
||||
return rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {});
|
||||
})
|
||||
.then((result) => {
|
||||
expect(result.results.length).toEqual(0);
|
||||
done();
|
||||
}, err => {
|
||||
fail('Should not throw error')
|
||||
});
|
||||
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
|
||||
.then(actualSchema => {
|
||||
expect(actualSchema.className).toEqual('ClientClassCreation');
|
||||
return rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {});
|
||||
})
|
||||
.then((result) => {
|
||||
expect(result.results.length).toEqual(0);
|
||||
done();
|
||||
}, () => {
|
||||
fail('Should not throw error')
|
||||
});
|
||||
});
|
||||
|
||||
it('query with wrongly encoded parameter', (done) => {
|
||||
rest.create(config, nobody, 'TestParameterEncode', {foo: 'bar'}
|
||||
).then(() => {
|
||||
return rest.create(config, nobody,
|
||||
'TestParameterEncode', {foo: 'baz'});
|
||||
'TestParameterEncode', {foo: 'baz'});
|
||||
}).then(() => {
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
|
||||
let p0 = rp.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
where: '{"foo":{"$ne": "baz"}}',
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}).then(fail, (response) => {
|
||||
let error = response.error;
|
||||
var b = JSON.parse(error);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
});
|
||||
|
||||
let p1 = rp.get({
|
||||
const p0 = rp.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}).then(fail, (response) => {
|
||||
let error = response.error;
|
||||
var b = JSON.parse(error);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
});
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?' + querystring.stringify({
|
||||
where: '{"foo":{"$ne": "baz"}}',
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
})
|
||||
.then(fail, (response) => {
|
||||
const error = response.error;
|
||||
var b = JSON.parse(error);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
});
|
||||
|
||||
const p1 = rp.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?' + querystring.stringify({
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
})
|
||||
.then(fail, (response) => {
|
||||
const error = response.error;
|
||||
var b = JSON.parse(error);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
});
|
||||
return Promise.all([p0, p1]);
|
||||
}).then(done).catch((err) => {
|
||||
}).then(done).catch((err) => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
@@ -237,21 +235,21 @@ describe('rest query', () => {
|
||||
});
|
||||
|
||||
it('makes sure null pointers are handed correctly #2189', done => {
|
||||
let object = new Parse.Object('AnObject');
|
||||
let anotherObject = new Parse.Object('AnotherObject');
|
||||
anotherObject.save().then(() => {
|
||||
const object = new Parse.Object('AnObject');
|
||||
const anotherObject = new Parse.Object('AnotherObject');
|
||||
anotherObject.save().then(() => {
|
||||
object.set('values', [null, null, anotherObject]);
|
||||
return object.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query('AnObject');
|
||||
}).then(() => {
|
||||
const query = new Parse.Query('AnObject');
|
||||
query.include('values');
|
||||
return query.first();
|
||||
}).then((result) => {
|
||||
let values = result.get('values');
|
||||
}).then((result) => {
|
||||
const values = result.get('values');
|
||||
expect(values.length).toBe(3);
|
||||
let anotherObjectFound = false;
|
||||
let nullCounts = 0;
|
||||
for(let value of values) {
|
||||
for(const value of values) {
|
||||
if (value === null) {
|
||||
nullCounts++;
|
||||
} else if (value instanceof Parse.Object) {
|
||||
@@ -261,7 +259,7 @@ describe('rest query', () => {
|
||||
expect(nullCounts).toBe(2);
|
||||
expect(anotherObjectFound).toBeTruthy();
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail(err);
|
||||
done();
|
||||
|
||||
@@ -16,97 +16,97 @@ function createUser() {
|
||||
|
||||
describe_only_db('mongo')('revocable sessions', () => {
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach((done) => {
|
||||
// Create 1 user with the legacy
|
||||
createUser().then(done);
|
||||
});
|
||||
|
||||
it('should upgrade legacy session token', done => {
|
||||
let user = Parse.Object.fromJSON({
|
||||
it('should upgrade legacy session token', done => {
|
||||
const user = Parse.Object.fromJSON({
|
||||
className: '_User',
|
||||
objectId: '1234567890',
|
||||
sessionToken: sessionToken
|
||||
});
|
||||
user._upgradeToRevocableSession().then((res) => {
|
||||
user._upgradeToRevocableSession().then((res) => {
|
||||
expect(res.getSessionToken().indexOf('r:')).toBe(0);
|
||||
const config = new Config(Parse.applicationId);
|
||||
// use direct access to the DB to make sure we're not
|
||||
// getting the session token stripped
|
||||
return config.database.loadSchema().then(schemaController => {
|
||||
return config.database.loadSchema().then(schemaController => {
|
||||
return schemaController.getOneSchema('_User', true)
|
||||
}).then((schema) => {
|
||||
return config.database.adapter.find('_User', schema, {objectId: '1234567890'}, {})
|
||||
}).then((results) => {
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].sessionToken).toBeUndefined();
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to become with revocable session token', done => {
|
||||
let user = Parse.Object.fromJSON({
|
||||
it('should be able to become with revocable session token', done => {
|
||||
const user = Parse.Object.fromJSON({
|
||||
className: '_User',
|
||||
objectId: '1234567890',
|
||||
sessionToken: sessionToken
|
||||
});
|
||||
user._upgradeToRevocableSession().then((res) => {
|
||||
user._upgradeToRevocableSession().then((res) => {
|
||||
expect(res.getSessionToken().indexOf('r:')).toBe(0);
|
||||
return Parse.User.logOut().then(() => {
|
||||
return Parse.User.become(res.getSessionToken())
|
||||
}).then((user) => {
|
||||
return Parse.User.logOut().then(() => {
|
||||
return Parse.User.become(res.getSessionToken())
|
||||
}).then((user) => {
|
||||
expect(user.id).toEqual('1234567890');
|
||||
});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not upgrade bad legacy session token', done => {
|
||||
it('should not upgrade bad legacy session token', done => {
|
||||
rp.post({
|
||||
url: Parse.serverURL+'/upgradeToRevocableSession',
|
||||
url: Parse.serverURL + '/upgradeToRevocableSession',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Rest-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': 'badSessionToken'
|
||||
},
|
||||
json: true
|
||||
}).then((res) => {
|
||||
}).then(() => {
|
||||
fail('should not be able to upgrade a bad token');
|
||||
}, (response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.error).not.toBeUndefined();
|
||||
expect(response.error.code).toBe(Parse.Error.INVALID_SESSION_TOKEN);
|
||||
expect(response.error.error).toEqual('invalid legacy session token');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not crash without session token #2720', done => {
|
||||
it('should not crash without session token #2720', done => {
|
||||
rp.post({
|
||||
url: Parse.serverURL+'/upgradeToRevocableSession',
|
||||
url: Parse.serverURL + '/upgradeToRevocableSession',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
},
|
||||
json: true
|
||||
}).then((res) => {
|
||||
}).then(() => {
|
||||
fail('should not be able to upgrade a bad token');
|
||||
}, (response) => {
|
||||
expect(response.statusCode).toBe(404);
|
||||
expect(response.error).not.toBeUndefined();
|
||||
expect(response.error.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
||||
expect(response.error.error).toEqual('invalid session');
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
+950
-512
File diff suppressed because it is too large
Load Diff
@@ -4,20 +4,21 @@ describe('SessionTokenCache', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
var Parse = require('parse/node');
|
||||
// Mock parse
|
||||
var mockUser = {
|
||||
become: jasmine.createSpy('become').and.returnValue(Parse.Promise.as({
|
||||
id: 'userId'
|
||||
}))
|
||||
}
|
||||
jasmine.mockLibrary('parse/node', 'User', mockUser);
|
||||
|
||||
spyOn(Parse, "Query").and.returnValue({
|
||||
first: jasmine.createSpy("first").and.returnValue(Parse.Promise.as(new Parse.Object("_Session", {
|
||||
user: new Parse.User({id:"userId"})
|
||||
}))),
|
||||
equalTo: function(){}
|
||||
})
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('can get undefined userId', function(done) {
|
||||
var sessionTokenCache = new SessionTokenCache();
|
||||
|
||||
sessionTokenCache.getUserId(undefined).then((userIdFromCache) => {
|
||||
sessionTokenCache.getUserId(undefined).then(() => {
|
||||
}, (error) => {
|
||||
expect(error).not.toBeNull();
|
||||
done();
|
||||
@@ -46,7 +47,4 @@ describe('SessionTokenCache', function() {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('parse/node', 'User');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
let twitter = require('../src/authDataManager/twitter');
|
||||
const twitter = require('../src/Adapters/Auth/twitter');
|
||||
|
||||
describe('Twitter Auth', () => {
|
||||
it('should use the proper configuration', () => {
|
||||
describe('Twitter Auth', () => {
|
||||
it('should use the proper configuration', () => {
|
||||
// Multiple options, consumer_key found
|
||||
expect(twitter.handleMultipleConfigurations({
|
||||
consumer_key: 'hello',
|
||||
@@ -9,10 +9,10 @@ describe('Twitter Auth', () => {
|
||||
consumer_key: 'hello'
|
||||
}, {
|
||||
consumer_key: 'world'
|
||||
}]).consumer_key).toEqual('hello')
|
||||
|
||||
}]).consumer_key).toEqual('hello');
|
||||
|
||||
// Multiple options, consumer_key not found
|
||||
expect(function(){
|
||||
expect(function(){
|
||||
twitter.handleMultipleConfigurations({
|
||||
consumer_key: 'some',
|
||||
}, [{
|
||||
@@ -23,7 +23,7 @@ describe('Twitter Auth', () => {
|
||||
}).toThrow();
|
||||
|
||||
// Multiple options, consumer_key not found
|
||||
expect(function(){
|
||||
expect(function(){
|
||||
twitter.handleMultipleConfigurations({
|
||||
auth_token: 'token',
|
||||
}, [{
|
||||
@@ -47,4 +47,18 @@ describe('Twitter Auth', () => {
|
||||
consumer_key: 'hello'
|
||||
}).consumer_key).toEqual('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail with missing options", (done) => {
|
||||
try {
|
||||
twitter.validateAuthData({
|
||||
consumer_key: 'key',
|
||||
consumer_secret: 'secret',
|
||||
auth_token: 'token',
|
||||
auth_token_secret: 'secret'
|
||||
}, undefined);
|
||||
} catch (error) {
|
||||
jequal(error.message, 'Twitter auth configuration missing');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+75
-76
@@ -1,103 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
var request = require('request');
|
||||
const Parse = require("parse/node");
|
||||
let Config = require('../src/Config');
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe('Uniqueness', function() {
|
||||
it('fail when create duplicate value in unique field', done => {
|
||||
let obj = new Parse.Object('UniqueField');
|
||||
const obj = new Parse.Object('UniqueField');
|
||||
obj.set('unique', 'value');
|
||||
obj.save().then(() => {
|
||||
expect(obj.id).not.toBeUndefined();
|
||||
let config = new Config('test');
|
||||
const config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueField', { fields: { unique: { __type: 'String' } } }, ['unique'])
|
||||
})
|
||||
.then(() => {
|
||||
let obj = new Parse.Object('UniqueField');
|
||||
obj.set('unique', 'value');
|
||||
return obj.save()
|
||||
}).then(() => {
|
||||
fail('Saving duplicate field should have failed');
|
||||
done();
|
||||
}, error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
const obj = new Parse.Object('UniqueField');
|
||||
obj.set('unique', 'value');
|
||||
return obj.save()
|
||||
}).then(() => {
|
||||
fail('Saving duplicate field should have failed');
|
||||
done();
|
||||
}, error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unique indexing works on pointer fields', done => {
|
||||
let obj = new Parse.Object('UniquePointer');
|
||||
const obj = new Parse.Object('UniquePointer');
|
||||
obj.save({ string: 'who cares' })
|
||||
.then(() => obj.save({ ptr: obj }))
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
|
||||
string: { __type: 'String' },
|
||||
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
|
||||
} }, ['ptr']);
|
||||
})
|
||||
.then(() => {
|
||||
let newObj = new Parse.Object('UniquePointer')
|
||||
newObj.set('ptr', obj)
|
||||
return newObj.save()
|
||||
})
|
||||
.then(() => {
|
||||
fail('save should have failed due to duplicate value');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
.then(() => obj.save({ ptr: obj }))
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
|
||||
string: { __type: 'String' },
|
||||
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
|
||||
} }, ['ptr']);
|
||||
})
|
||||
.then(() => {
|
||||
const newObj = new Parse.Object('UniquePointer')
|
||||
newObj.set('ptr', obj)
|
||||
return newObj.save()
|
||||
})
|
||||
.then(() => {
|
||||
fail('save should have failed due to duplicate value');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when attempting to ensure uniqueness of fields that are not currently unique', done => {
|
||||
let o1 = new Parse.Object('UniqueFail');
|
||||
const o1 = new Parse.Object('UniqueFail');
|
||||
o1.set('key', 'val');
|
||||
let o2 = new Parse.Object('UniqueFail');
|
||||
const o2 = new Parse.Object('UniqueFail');
|
||||
o2.set('key', 'val');
|
||||
Parse.Object.saveAll([o1, o2])
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('can do compound uniqueness', done => {
|
||||
let config = new Config('test');
|
||||
const config = new Config('test');
|
||||
config.database.adapter.ensureUniqueness('CompoundUnique', { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } }, ['k1', 'k2'])
|
||||
.then(() => {
|
||||
let o1 = new Parse.Object('CompoundUnique');
|
||||
o1.set('k1', 'v1');
|
||||
o1.set('k2', 'v2');
|
||||
return o1.save();
|
||||
})
|
||||
.then(() => {
|
||||
let o2 = new Parse.Object('CompoundUnique');
|
||||
o2.set('k1', 'v1');
|
||||
o2.set('k2', 'not a dupe');
|
||||
return o2.save();
|
||||
})
|
||||
.then(() => {
|
||||
let o3 = new Parse.Object('CompoundUnique');
|
||||
o3.set('k1', 'not a dupe');
|
||||
o3.set('k2', 'v2');
|
||||
return o3.save();
|
||||
})
|
||||
.then(() => {
|
||||
let o4 = new Parse.Object('CompoundUnique');
|
||||
o4.set('k1', 'v1');
|
||||
o4.set('k2', 'v2');
|
||||
return o4.save();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
.then(() => {
|
||||
const o1 = new Parse.Object('CompoundUnique');
|
||||
o1.set('k1', 'v1');
|
||||
o1.set('k2', 'v2');
|
||||
return o1.save();
|
||||
})
|
||||
.then(() => {
|
||||
const o2 = new Parse.Object('CompoundUnique');
|
||||
o2.set('k1', 'v1');
|
||||
o2.set('k2', 'not a dupe');
|
||||
return o2.save();
|
||||
})
|
||||
.then(() => {
|
||||
const o3 = new Parse.Object('CompoundUnique');
|
||||
o3.set('k1', 'not a dupe');
|
||||
o3.set('k2', 'v2');
|
||||
return o3.save();
|
||||
})
|
||||
.then(() => {
|
||||
const o4 = new Parse.Object('CompoundUnique');
|
||||
o4.set('k1', 'v1');
|
||||
o4.set('k2', 'v2');
|
||||
return o4.save();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
var UserController = require('../src/Controllers/UserController').UserController;
|
||||
var emailAdapter = require('./MockEmailAdapter')
|
||||
var AppCache = require('../src/cache').AppCache;
|
||||
|
||||
describe('UserController', () => {
|
||||
var user = {
|
||||
_email_verify_token: 'testToken',
|
||||
username: 'testUser',
|
||||
email: 'test@example.com'
|
||||
}
|
||||
|
||||
describe('sendVerificationEmail', () => {
|
||||
describe('parseFrameURL not provided', () => {
|
||||
it('uses publicServerURL', (done) => {
|
||||
|
||||
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
|
||||
publicServerURL: 'http://www.example.com',
|
||||
customPages: {
|
||||
parseFrameURL: undefined
|
||||
}
|
||||
}))
|
||||
|
||||
emailAdapter.sendVerificationEmail = (options) => {
|
||||
expect(options.link).toEqual('http://www.example.com/apps/test/verify_email?token=testToken&username=testUser')
|
||||
done()
|
||||
}
|
||||
|
||||
var userController = new UserController(emailAdapter, 'test', {
|
||||
verifyUserEmails: true
|
||||
})
|
||||
|
||||
userController.sendVerificationEmail(user)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseFrameURL provided', () => {
|
||||
it('uses parseFrameURL and includes the destination in the link parameter', (done) => {
|
||||
|
||||
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
|
||||
publicServerURL: 'http://www.example.com',
|
||||
customPages: {
|
||||
parseFrameURL: 'http://someother.example.com/handle-parse-iframe'
|
||||
}
|
||||
}))
|
||||
|
||||
emailAdapter.sendVerificationEmail = (options) => {
|
||||
expect(options.link).toEqual('http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=testToken&username=testUser')
|
||||
done()
|
||||
}
|
||||
|
||||
var userController = new UserController(emailAdapter, 'test', {
|
||||
verifyUserEmails: true
|
||||
})
|
||||
|
||||
userController.sendVerificationEmail(user)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,503 @@
|
||||
'use strict';
|
||||
|
||||
const Parse = require('parse/node');
|
||||
const request = require('request-promise');
|
||||
|
||||
// const Config = require('../src/Config');
|
||||
|
||||
const EMAIL = 'foo@bar.com';
|
||||
const ZIP = '10001';
|
||||
const SSN = '999-99-9999';
|
||||
|
||||
describe('Personally Identifiable Information', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(done => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => user
|
||||
.set('email', EMAIL)
|
||||
.set('zip', ZIP)
|
||||
.set('ssn', SSN)
|
||||
.save())
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should be able to get own PII via API with object', (done) => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
|
||||
it('should not be able to get PII via API with object', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
})
|
||||
.fail(e => {
|
||||
done.fail(JSON.stringify(e));
|
||||
})
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get PII via API with object using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch({ useMasterKey: true }).then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Find', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Find', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Find using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first({ useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Get', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Get', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Get using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id, { useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get PII via REST', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST with self credentials', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST using master key', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should not get PII via REST by ID', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with self credentials', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with master key', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
describe('with configured sensitive fields', () => {
|
||||
beforeEach((done) => {
|
||||
reconfigureServer({ userSensitiveFields: ['ssn', 'zip'] })
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should be able to get own PII via API with object', (done) => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
}, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not be able to get PII via API with object', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get PII via API with object using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch({ useMasterKey: true }).then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Find', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Find', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Find using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first({ useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Get', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Get', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Get using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id, { useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get PII via REST', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(undefined);
|
||||
expect(fetchedUser.ssn).toBe(undefined);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST with self credentials', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
expect(fetchedUser.ssn).toBe(SSN);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST using master key', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
expect(fetchedUser.ssn).toBe(SSN);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should not get PII via REST by ID', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(undefined);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with self credentials', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with master key', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
var Parse = require('parse/node').Parse;
|
||||
var request = require('request');
|
||||
|
||||
describe('info logs', () => {
|
||||
|
||||
it("Verify INFO logs", (done) => {
|
||||
var winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
winstonLoggerAdapter.log('info', 'testing info logs', () => {
|
||||
var winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
winstonLoggerAdapter.log('info', 'testing info logs', () => {
|
||||
winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'info'
|
||||
}, (results) => {
|
||||
if (results.length == 0) {
|
||||
fail('The adapter should return non-empty results');
|
||||
} else {
|
||||
expect(results[0].message).toEqual('testing info logs');
|
||||
}
|
||||
// Check the error log
|
||||
// Regression #2639
|
||||
winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'info'
|
||||
level: 'error'
|
||||
}, (results) => {
|
||||
if (results.length == 0) {
|
||||
fail('The adapter should return non-empty results');
|
||||
} else {
|
||||
expect(results[0].message).toEqual('testing info logs');
|
||||
}
|
||||
// Check the error log
|
||||
// Regression #2639
|
||||
winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'error'
|
||||
}, (results) => {
|
||||
expect(results.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
expect(results.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error logs', () => {
|
||||
@@ -59,42 +58,42 @@ describe('error logs', () => {
|
||||
describe('verbose logs', () => {
|
||||
it("mask sensitive information in _User class", (done) => {
|
||||
reconfigureServer({ verbose: true })
|
||||
.then(() => createTestUser())
|
||||
.then(() => {
|
||||
let winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
return winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'verbose'
|
||||
});
|
||||
}).then((results) => {
|
||||
let logString = JSON.stringify(results);
|
||||
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
|
||||
expect(logString.match(/moon-y/g)).toBe(null);
|
||||
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/login?username=test&password=moon-y'
|
||||
}, (error, response, body) => {
|
||||
let winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
.then(() => createTestUser())
|
||||
.then(() => {
|
||||
const winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
return winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'verbose'
|
||||
}).then((results) => {
|
||||
let logString = JSON.stringify(results);
|
||||
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
|
||||
expect(logString.match(/moon-y/g)).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
})
|
||||
}).then((results) => {
|
||||
const logString = JSON.stringify(results);
|
||||
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
|
||||
expect(logString.match(/moon-y/g)).toBe(null);
|
||||
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/login?username=test&password=moon-y'
|
||||
}, () => {
|
||||
const winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
return winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'verbose'
|
||||
}).then((results) => {
|
||||
const logString = JSON.stringify(results);
|
||||
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
|
||||
expect(logString.match(/moon-y/g)).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user